kaxil commented on code in PR #70132:
URL: https://github.com/apache/airflow/pull/70132#discussion_r3680650264


##########
providers/common/ai/src/airflow/providers/common/ai/operators/agent.py:
##########
@@ -493,16 +492,11 @@ def execute(self, context: Context) -> Any:
                 output,
                 message_history=result.all_messages(),
             )
-            if isinstance(self.output_type, type) and 
issubclass(self.output_type, BaseModel):
-                return rehydrate_pydantic_output(
-                    self.output_type,
-                    result_str,
-                    serialize_output=self._serialize_model_output,
-                )
-            try:
-                return json.loads(result_str)
-            except (ValueError, TypeError):
-                return result_str
+            return rehydrate_pydantic_output(

Review Comment:
   You're right about #70075 -- it is an ancestor of this branch, and 
`rehydrate_pydantic_output(list[str], '["tag-a","tag-b"]', 
serialize_output=False)` does return `['tag-a', 'tag-b']`. My reasoning for 
`list[str]`/`bool`/`dict` was wrong.
   
   The old `isinstance(self.output_type, type)` check was also guarding the 
case where `output_type` isn't a plain type at all. `AgentOperator` passes it 
straight through to `Agent(...)` (agent.py:342 -> hooks/pydantic_ai.py:279), so 
pydantic-ai's multi-output and marker forms land here, and `TypeAdapter` fails 
on them outside the helper's `except (ValidationError, ValueError, TypeError)`:
   
   ```
   output_type=[Foo, Bar]      -> PydanticSchemaGenerationError   
(PydanticUserError -> RuntimeError, so not caught)
   output_type=ToolOutput(Foo) -> AttributeError: 'ToolOutput' object has no 
attribute '__mro__'
   ```
   
   On pydantic-ai 2.13 / pydantic 2.13.4, `Agent(TestModel(), output_type=[Foo, 
Bar])` returns `Foo(a=0)` and `_to_string` gives `'{"a":0}'`. Before this PR 
that fell through to `json.loads` and came back as `{'a': 0}`; now it raises, 
after the model call and after the reviewer has already approved, so the 
approved output is lost.
   
   Widening the helper's `except` doesn't get you back to the old result -- it 
returns `raw`, so callers get `'{"a":0}'` instead of the dict. Separating 
schema-build failure from validation does:
   
   ```python
   try:
       adapter = TypeAdapter(output_type)
   except Exception:
       # No pydantic schema for this output_type: 
ToolOutput/NativeOutput/PromptedOutput
       # markers, output functions, or a `[A, B]` union list. Fall back to 
plain JSON.
       try:
           return json.loads(raw)
       except (ValueError, TypeError):
           return raw
   try:
       return adapter.validate_json(raw)
   except (ValidationError, ValueError, TypeError):
       return raw
   ```
   
   Putting it in `utils/output_type.py` rather than here also covers 
`LLMOperator`'s `require_approval` path, which calls the helper at llm.py:187.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to