fix(advanced): send Gemini a function calling mode on subagent calls - #1071
Conversation
There was a problem hiding this comment.
🟢 Approval recommended
The middleware change is narrowly scoped, aligns advanced-agent behavior with the already-established payload handler flow, and is backed by comprehensive new/updated tests for the reported failure modes.
Pull request overview
This PR fixes advanced-agent + subagent interactions with Gemini (notably gemini-2.5-flash) by ensuring deep-agent model calls are shaped/validated via the same provider payload handler logic used in the ReAct path, preventing malformed function-call outputs from being silently accepted as empty “final answers”.
Changes:
- Add
_PayloadHandlerMiddlewareto (a) inject Geminifunction_calling_configinVALIDATEDmode whentool_choiceis unset, and (b) validate provider stop reasons + reject empty non-reasoning, non-tool-call responses. - Ensure the middleware is applied to both the main advanced agent and declarative subagents (including the general-purpose subagent) via
_subagents_with_middleware. - Add targeted tests for tool-config injection, stop-reason validation, empty-answer rejection, and subagent wiring; update existing middleware-count assertions to focus on the runtime-prompt middleware.
File summaries
| File | Description |
|---|---|
src/uipath_langchain/agent/advanced/agent.py |
Adds payload-handler middleware, wires it into deep-agent middleware and subagent specs, and rejects empty responses. |
tests/agent/advanced/test_payload_handler_middleware.py |
New unit tests covering tool_config injection, stop-reason checks, empty-answer rejection, and subagent wiring. |
tests/agent/advanced/test_create_advanced_agent_graph.py |
Updates assertions to select the runtime system-prompt middleware rather than assuming it’s the only middleware. |
tests/agent/advanced/test_conversational_advanced_agent_graph.py |
Same as above for the conversational advanced agent graph tests. |
pyproject.toml |
Bumps project version to 0.17.5. |
uv.lock |
Updates locked package version to 0.17.5. |
Review details
- Files reviewed: 5/6 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
3140b99 to
e0bd636
Compare
An advanced agent's subagent turn carries no forced tool_choice, so the request reached Gemini with no function_calling_config at all and Vertex fell back to AUTO. gemini-2.5-flash then answered with Python source instead of a function call, finishing MALFORMED_FUNCTION_CALL. Only the main agent was spared, because its structured-output response format forces tool_choice on every call. Nothing on the advanced path called check_stop_reason either, so that malformed finish reason degraded into an empty message that reads as a final answer: the subagent handed back an empty result and the caller either looped to its recursion limit or reported success it had not achieved. One middleware now routes deep-agent calls through the payload handler the react path already uses. It injects the handler's tool_config only when tool_choice is unset, matching langchain_google_genai's own gate so the two can never conflict, and it checks the finish reason for every provider. A turn with no text, no tool call, and no reasoning is refused rather than accepted as an answer. create_deep_agent gives its middleware argument to the main agent alone and assembles the general-purpose subagent itself, so the subagent is reached through its spec. deepagents still builds that subagent's own middleware stack, tools, model, and prompt around ours.
e0bd636 to
564c2f4
Compare
main released 0.18.2 while this branch was carrying a bump to that same version, so the rebase resolved the bump away and left the branch with none. 0.18.0 through 0.18.2 are published.
|



Summary
function_calling_configmode on subagent calls, sogemini-2.5-flashstops replying with Python source instead of a tool callWhy
a deepagents subagent has no response format, so nothing forced a tool choice and vertex fell back to
AUTO. the malformed reply came back empty, read as a final answer, and the run either looped to its recursion limit or reported success with the subagent having contributed nothing.