fix: use string tool_choice for Groq and OpenRouter (#9267)

Some providers (Groq, OpenRouter proxied providers) only support string
values for tool_choice ("none", "auto", "required"), not the object
format {"type": "function", "name": "..."}.

When force_tool_call is set, convert to "required" instead of object
format for these providers.

🤖 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
cthomas
2026-02-04 12:29:43 -08:00
committed by Caren Thomas
parent e0a23f7039
commit 09d7940090
2 changed files with 11 additions and 1 deletions

View File

@@ -34,6 +34,11 @@ class GroqClient(OpenAIClient):
) -> dict:
data = super().build_request_data(agent_type, messages, llm_config, tools, force_tool_call, requires_subsequent_tool_call)
# Groq only supports string values for tool_choice: "none", "auto", "required"
# Convert object-format tool_choice (used for force_tool_call) to "required"
if "tool_choice" in data and isinstance(data["tool_choice"], dict):
data["tool_choice"] = "required"
# Groq validation - these fields are not supported and will cause 400 errors
# https://console.groq.com/docs/openai
if "top_logprobs" in data:

View File

@@ -468,7 +468,12 @@ class OpenAIClient(LLMClientBase):
tool_choice = None
if tools: # only set tool_choice if tools exist
if force_tool_call is not None:
tool_choice = ToolFunctionChoice(type="function", function=ToolFunctionChoiceFunctionCall(name=force_tool_call))
# OpenRouter proxies to providers that may not support object-format tool_choice
# Use "required" instead which achieves similar effect
if is_openrouter:
tool_choice = "required"
else:
tool_choice = ToolFunctionChoice(type="function", function=ToolFunctionChoiceFunctionCall(name=force_tool_call))
elif requires_subsequent_tool_call:
tool_choice = "required"
elif self.requires_auto_tool_choice(llm_config) or agent_type == AgentType.letta_v1_agent: