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:
   Half of this is mine to withdraw: you're right that #70075 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']`. For `list[str]`, 
`bool` and `dict` my reasoning was wrong, and the `json.loads` fallback is 
genuinely redundant for those.
   
   The objection survives for the `output_type` forms that aren't a plain 
`type`, which is what the old `isinstance(self.output_type, type)` branch was 
routing around. `AgentOperator` forwards `output_type` verbatim to `Agent(...)` 
(agent.py:342 -> hooks/pydantic_ai.py:279), so pydantic-ai's multi-output and 
marker forms reach this line, and `TypeAdapter` fails on them outside the 
helper's `except (ValidationError, ValueError, TypeError)`:
   
   ```
   output_type=[Foo, Bar]      -> PydanticSchemaGenerationError   (MRO is 
PydanticUserError -> RuntimeError, not TypeError)
   output_type=ToolOutput(Foo) -> AttributeError: 'ToolOutput' object has no 
attribute '__mro__'
   ```
   
   Verified on pydantic-ai 2.13 / pydantic 2.13.4: `Agent(TestModel(), 
output_type=[Foo, Bar])` runs and returns `Foo(a=0)`, `_to_string` gives 
`'{"a":0}'`, the old branch fell through to `json.loads` and returned `{'a': 
0}`, and the new unconditional call raises. Same for `ToolOutput`. So those 
configs go from working to a hard task failure -- and it raises after the LLM 
spend and after the reviewer already clicked approve, so the approved output is 
lost.
   
   Restoring `json.loads` at the call site isn't the fix I'd suggest, but 
widening the helper's `except` isn't equivalent either: it falls through to 
`return raw` and hands downstream `'{"a":0}'` instead of the dict it used to 
get. Splitting schema-build failure from validation failure keeps the old 
result:
   
   ```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
   ```
   
   Doing it in `utils/output_type.py` rather than here also closes the same 
hole on `LLMOperator`'s `require_approval` path, which already 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