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: