fix: stop output_pydantic from leaking into tool-calling loop (#5472)#5821
fix: stop output_pydantic from leaking into tool-calling loop (#5472)#5821mahasarabesh wants to merge 2 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughThis PR prevents Task.output_pydantic/output_json from setting the agent executor's response_model and ensures native tool-calling paths call get_llm_response with response_model=None so structured-output validation does not interfere with tool invocation. ChangesResponse Model Separation from Tool-Calling Loop
Sequence Diagram(s)sequenceDiagram
participant AgentExecutor
participant LLM
participant ToolRunner
AgentExecutor->>LLM: get_llm_response(..., response_model=None)
LLM->>AgentExecutor: return tool-call list OR final-answer
AgentExecutor->>ToolRunner: run indicated tools (if any)
ToolRunner->>AgentExecutor: tool outputs
AgentExecutor->>LLM: (final formatting or post-process) optional
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
…Inc#5472) Since v1.9.0, Task(output_pydantic=...) and Task(output_json=...) get mapped onto response_model in the agent executor. This means every LLM call in the tool loop sends both tools and response_format at the same time. Non-OpenAI providers (vLLM, Gemini, Anthropic, LiteLLM) treat response_format as higher priority - the LLM returns structured JSON immediately and never calls any tools. Fix: - agent/core.py: Only task.response_model (explicit opt-in) gets passed as response_model. output_pydantic/output_json stay in post-processing. - crew_agent_executor.py: Pass response_model=None in native tool loops (sync + async) so structured output never competes with tools. - experimental/agent_executor.py: Same fix for call_llm_native_tools(). - 4 regression tests added.
fc9f4c2 to
3ecf73d
Compare
Fixes #5472
What's wrong
Since v1.9.0,
Task(output_pydantic=...)andTask(output_json=...)get mapped ontoresponse_modelin the agent executor. This means every LLM call in the tool loop sends bothtoolsandresponse_formatat the same time.OpenAI kinda handles this, but vLLM, Gemini, Anthropic, and anything going through LiteLLM's
InternalInstructor-- they all treatresponse_formatas higher priority. The LLM returns structured JSON immediately and never calls any tools.The post-processing path (
Task._export_output()->convert_to_model()) already handles converting raw output to Pydantic models after the loop. That's how it worked before v1.9.0 and it works fine.What this PR does
agent/core.py-- Onlytask.response_model(explicit opt-in) gets passed asresponse_model.output_pydanticandoutput_jsonstay in post-processing where they belong.crew_agent_executor.py-- Passresponse_model=Nonein_invoke_loop_native_tools(sync + async). When tools are present, structured output shouldn't compete with them.experimental/agent_executor.py-- Same fix forcall_llm_native_tools().Tests -- 4 regression tests covering
output_pydantic,output_json, explicitresponse_model, and_update_executor_parameters.How I tested
test_agent.pysuite passes (84 passed, pre-existing failures from missing optional deps unchanged)Note
There are two existing PRs for this -- #5680 (stops the mapping at source) and #5767 (suppresses during loop iterations). This PR combines both approaches: fix the mapping AND defensively suppress during tool loops as a safety net.
Summary by CodeRabbit
Bug Fixes
Tests