fix(agent_tool): wrap input_schema payload in ReAct prompt and propagate tool_choice to LiteLLM#5924
Conversation
…ol_choice to LiteLLM When AgentTool uses input_schema, the inner agent receives a raw JSON blob that causes Claude models to skip the tool-calling loop (ReAct). Fix by wrapping the payload in a natural-language instruction. Also propagate tool_config.function_calling_config.mode to LiteLLM's tool_choice parameter so callers can enforce tool_choice='required'. Addresses google#773. Fixes: google#5926
ded18ca to
6168c49
Compare
|
Response from ADK Triaging Agent Hello @ecanlar, thank you for creating this PR! We appreciate your contribution to fixing the LiteLLM/Claude Currently, the following items are missing:
Providing this information helps ensure high code quality, prevents future regressions, and makes the review process smoother. Thank you! |
…tool_choice propagation - Update existing _get_completion_inputs call sites to handle new 5-tuple return value (adds tool_choice as 5th element) - Add 3 tests for AgentTool.run_async: verifies message is passed verbatim without input_schema, and is wrapped in a natural-language instruction with input_schema (PR google#5924 fix) - Add 8 tests for LiteLLM tool_choice propagation: covers _get_completion_inputs returning correct tool_choice for ANY/NONE/AUTO modes and None when no tool_config; covers generate_content_async correctly including/omitting tool_choice in completion_args
- Fix: only apply the ReAct wrapper in agent_tool.py when output_schema is not set on the inner agent, preventing breaking of single-shot structured output mode - Add regression test test_run_async_with_input_and_output_schema_passes_raw_json documenting that raw JSON is passed when both input_schema and output_schema are set - Apply pre-commit formatting fixes (isort + pyink)
23d582a to
67d087a
Compare
…st HTTP call Move the ValueError from module import time to the lazy _get_session() factory so that importing the package (e.g. pytest collection) does not fail in environments where GITHUB_TOKEN is not set.
f8c61e3 to
7c23d83
Compare
|
Good catch — confirmed the guard was not in place. Fixed in the latest commit:
Two new tests cover both paths; 10 |
|
Hi @ecanlar , Thank you for your contribution! We appreciate you taking the time to submit this pull request. |
|
/adk-pr-analyze |
Summary
Fixes two issues that prevent Claude-family models from entering the ReAct tool-calling loop when used via LiteLLM inside a nested
AgentTool:AgentToolwithinput_schema— the serialized JSON payload sent as the first message causes Claude to interpret the request as already complete and respond directly without calling any tools.tool_choicenot propagated —llm_request.config.tool_config.function_calling_config.modewas not forwarded to LiteLLM'scompletion_args, so callers could not enforce tool use at the request level.Changes
src/google/adk/tools/agent_tool.pyWrap the serialized
input_schemaJSON in a natural-language instruction that explicitly asks the inner agent to use its available tools before producing a response. This keeps Claude in ReAct mode regardless of the message content format.src/google/adk/models/lite_llm.pyRead
llm_request.config.tool_config.function_calling_config.modeand map it to LiteLLM'stool_choiceparameter:ANY→"required"NONE→"none"AUTO→ provider default (unchanged, key omitted fromcompletion_args)_get_completion_inputsnow returns a 5-tuple(messages, tools, response_format, generation_params, tool_choice).Unit Tests Added
tests/unittests/tools/test_agent_tool.pytest_run_async_no_input_schema_passes_request_unchanged: withoutinput_schema, the content passed to the inner runner isargs['request']verbatim.test_run_async_with_input_schema_wraps_in_natural_language: withinput_schema, the text begins with"Process the following structured request", contains"Request:\n"followed by the JSON payload, and is not a bare JSON blob.test_run_async_with_input_schema_text_not_raw_json: asserts the text does not start with{.tests/unittests/models/test_litellm.pytest_get_completion_inputs_tool_choice_none_without_tool_config:tool_choiceisNonewith notool_config.test_get_completion_inputs_tool_choice_required_for_any_mode: returns"required"forANYmode.test_get_completion_inputs_tool_choice_none_for_none_mode: returns"none"forNONEmode.test_get_completion_inputs_tool_choice_none_for_auto_mode: returnsNoneforAUTOmode.test_generate_content_async_propagates_tool_choice_required:acompletionreceivestool_choice="required"forANY.test_generate_content_async_propagates_tool_choice_none_mode:acompletionreceivestool_choice="none"forNONE.test_generate_content_async_omits_tool_choice_for_auto_mode:tool_choicekey absent fromcompletion_argsforAUTO.test_generate_content_async_omits_tool_choice_without_tool_config:tool_choicekey absent when notool_config.Also updated all existing
_get_completion_inputscall sites (10 occurrences) to unpack the new 5-tuple.Pytest Results
The 1 pre-existing failure (
test_custom_schema[GOOGLE_AI]) is apydantic.ValidationErrorthat reproduces on the base branch before any of these changes and is unrelated to this fix.Related