From 09d7940090ab630f1c48224bc54ce20c3865ae76 Mon Sep 17 00:00:00 2001 From: cthomas Date: Wed, 4 Feb 2026 12:29:43 -0800 Subject: [PATCH] fix: use string tool_choice for Groq and OpenRouter (#9267) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- letta/llm_api/groq_client.py | 5 +++++ letta/llm_api/openai_client.py | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/letta/llm_api/groq_client.py b/letta/llm_api/groq_client.py index 5909f4d5..34edf622 100644 --- a/letta/llm_api/groq_client.py +++ b/letta/llm_api/groq_client.py @@ -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: diff --git a/letta/llm_api/openai_client.py b/letta/llm_api/openai_client.py index eb61ed33..d7cce539 100644 --- a/letta/llm_api/openai_client.py +++ b/letta/llm_api/openai_client.py @@ -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: