Skip to content

Commit 6168c49

Browse files
committed
fix(agent_tool): wrap input_schema JSON in ReAct prompt; propagate tool_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 #773. Fixes: #5926
1 parent 4006fe4 commit 6168c49

2 files changed

Lines changed: 29 additions & 5 deletions

File tree

src/google/adk/models/lite_llm.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1883,6 +1883,7 @@ async def _get_completion_inputs(
18831883
Optional[List[Dict]],
18841884
Optional[Dict[str, Any]],
18851885
Optional[Dict],
1886+
Optional[str],
18861887
]:
18871888
"""Converts an LlmRequest to litellm inputs and extracts generation params.
18881889
@@ -1891,8 +1892,8 @@ async def _get_completion_inputs(
18911892
model: The model string to use for determining provider-specific behavior.
18921893
18931894
Returns:
1894-
The litellm inputs (message list, tool dictionary, response format and
1895-
generation params).
1895+
The litellm inputs (message list, tool dictionary, response format,
1896+
generation params, and tool_choice).
18961897
"""
18971898
_ensure_litellm_imported()
18981899

@@ -1967,7 +1968,21 @@ async def _get_completion_inputs(
19671968
if not generation_params:
19681969
generation_params = None
19691970

1970-
return messages, tools, response_format, generation_params
1971+
# 5. Extract tool_choice from tool_config
1972+
tool_choice: Optional[str] = None
1973+
if (
1974+
llm_request.config
1975+
and llm_request.config.tool_config
1976+
and llm_request.config.tool_config.function_calling_config
1977+
):
1978+
mode = llm_request.config.tool_config.function_calling_config.mode
1979+
if mode == types.FunctionCallingConfigMode.ANY:
1980+
tool_choice = "required"
1981+
elif mode == types.FunctionCallingConfigMode.NONE:
1982+
tool_choice = "none"
1983+
# AUTO → None (provider default)
1984+
1985+
return messages, tools, response_format, generation_params, tool_choice
19711986

19721987

19731988
def _build_function_declaration_log(
@@ -2228,7 +2243,7 @@ async def generate_content_async(
22282243
logger.debug(_build_request_log(llm_request))
22292244

22302245
effective_model = llm_request.model or self.model
2231-
messages, tools, response_format, generation_params = (
2246+
messages, tools, response_format, generation_params, tool_choice = (
22322247
await _get_completion_inputs(llm_request, effective_model)
22332248
)
22342249
normalized_messages = _normalize_ollama_chat_messages(
@@ -2260,6 +2275,9 @@ async def generate_content_async(
22602275
if generation_params:
22612276
completion_args.update(generation_params)
22622277

2278+
if tool_choice is not None:
2279+
completion_args["tool_choice"] = tool_choice
2280+
22632281
if stream:
22642282
text = ""
22652283
reasoning_parts: List[types.Part] = []

src/google/adk/tools/agent_tool.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,17 @@ async def run_async(
217217
input_schema = _get_input_schema(self.agent)
218218
if input_schema:
219219
input_value = input_schema.model_validate(args)
220+
json_payload = input_value.model_dump_json(exclude_none=True)
220221
content = types.Content(
221222
role='user',
222223
parts=[
223224
types.Part.from_text(
224-
text=input_value.model_dump_json(exclude_none=True)
225+
text=(
226+
'Process the following structured request. Use your'
227+
' available tools as needed to gather information or'
228+
' perform actions before producing the final'
229+
' response.\n\nRequest:\n' + json_payload
230+
)
225231
)
226232
],
227233
)

0 commit comments

Comments
 (0)