Skip to content

Commit 71ef1ab

Browse files
fix: resolve OpenRouter XML tool call routing issue
- Remove tools parameter for XML format models to avoid provider routing errors - Add tool descriptions and XML format instructions to system prompt - Maintain backward compatibility with existing functionality - Fix issue where OpenRouter couldn''t find providers supporting tools for Qwen models Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
1 parent f122e23 commit 71ef1ab

2 files changed

Lines changed: 122 additions & 13 deletions

File tree

src/praisonai-agents/praisonaiagents/llm/llm.py

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,19 @@ def _build_messages(self, prompt, system_prompt=None, chat_history=None, output_
722722
if schema_model and hasattr(schema_model, 'model_json_schema'):
723723
system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {json.dumps(schema_model.model_json_schema())}"
724724

725+
# For XML format models, add tool descriptions to system prompt
726+
if tools and self._supports_xml_tool_format():
727+
system_prompt += "\n\nYou have access to the following tools:"
728+
for tool in tools:
729+
if isinstance(tool, dict) and 'function' in tool:
730+
func = tool['function']
731+
name = func.get('name', 'unknown')
732+
description = func.get('description', 'No description available')
733+
system_prompt += f"\n- {name}: {description}"
734+
735+
system_prompt += "\n\nWhen you need to use a tool, wrap your tool call in XML tags like this:"
736+
system_prompt += "\n<tool_call>\n{\"name\": \"tool_name\", \"arguments\": {\"param\": \"value\"}}\n</tool_call>"
737+
725738
# Skip system messages for legacy o1 models as they don't support them
726739
if not self._needs_system_message_skip():
727740
messages.append({"role": "system", "content": system_prompt})
@@ -3155,19 +3168,28 @@ def _build_completion_params(self, **override_params) -> Dict[str, Any]:
31553168

31563169
logging.debug(f"Using Gemini native structured output with schema: {json.dumps(schema, indent=2)}")
31573170

3158-
# Add tool_choice="auto" when tools are provided (unless already specified)
3159-
if 'tools' in params and params['tools'] and 'tool_choice' not in params:
3160-
# For Gemini models, use tool_choice to encourage tool usage
3161-
if self._is_gemini_model():
3162-
try:
3163-
import litellm
3164-
# Check if model supports function calling before setting tool_choice
3165-
if litellm.supports_function_calling(model=self.model):
3166-
params['tool_choice'] = 'auto'
3167-
except Exception as e:
3168-
# If check fails, still set tool_choice for known Gemini models
3169-
logging.debug(f"Could not verify function calling support: {e}. Setting tool_choice anyway.")
3170-
params['tool_choice'] = 'auto'
3171+
# Handle XML format models (like Qwen) differently for tool calls
3172+
if 'tools' in params and params['tools']:
3173+
if self._supports_xml_tool_format():
3174+
# For XML format models, remove tools parameter to avoid OpenRouter routing issues
3175+
# Tools will be described in the system prompt instead
3176+
logging.debug("Removing tools parameter for XML format model to avoid provider routing issues")
3177+
params.pop('tools', None)
3178+
params.pop('tool_choice', None)
3179+
else:
3180+
# Add tool_choice="auto" when tools are provided (unless already specified)
3181+
if 'tool_choice' not in params:
3182+
# For Gemini models, use tool_choice to encourage tool usage
3183+
if self._is_gemini_model():
3184+
try:
3185+
import litellm
3186+
# Check if model supports function calling before setting tool_choice
3187+
if litellm.supports_function_calling(model=self.model):
3188+
params['tool_choice'] = 'auto'
3189+
except Exception as e:
3190+
# If check fails, still set tool_choice for known Gemini models
3191+
logging.debug(f"Could not verify function calling support: {e}. Setting tool_choice anyway.")
3192+
params['tool_choice'] = 'auto'
31713193

31723194
return params
31733195

test_openrouter_xml_fix.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/usr/bin/env python3
2+
"""Test script for OpenRouter XML tool call fix"""
3+
4+
import os
5+
import sys
6+
7+
# Add the source directory to the path
8+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src', 'praisonai-agents'))
9+
10+
from praisonaiagents import Agent
11+
12+
def get_weather(city: str) -> str:
13+
"""Get weather information for a city"""
14+
return f"The weather in {city} is sunny with 22°C"
15+
16+
def main():
17+
print("Testing OpenRouter XML tool call fix...")
18+
19+
# Test with auto-detection (should detect Qwen as XML format)
20+
agent = Agent(
21+
instructions="You are a helpful assistant",
22+
llm="openrouter/qwen/qwen-2.5-7b-instruct",
23+
tools=[get_weather],
24+
verbose=True
25+
)
26+
27+
print("Created agent with Qwen model...")
28+
29+
# Get the LLM instance directly from the agent
30+
llm_instance = agent.llm_instance # This should be the LLM object
31+
print(f"XML tool format supported: {llm_instance._supports_xml_tool_format()}")
32+
33+
# Test the tool call without actually making API request
34+
# We'll just verify the parameters are built correctly
35+
test_tools = [
36+
{
37+
"type": "function",
38+
"function": {
39+
"name": "get_weather",
40+
"description": "Get weather information for a city",
41+
"parameters": {
42+
"type": "object",
43+
"properties": {
44+
"city": {
45+
"type": "string",
46+
"description": "The city name"
47+
}
48+
},
49+
"required": ["city"]
50+
}
51+
}
52+
}
53+
]
54+
55+
# Test _build_completion_params
56+
params = llm_instance._build_completion_params(
57+
messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
58+
tools=test_tools,
59+
temperature=0.2
60+
)
61+
62+
print("\n=== Completion Parameters ===")
63+
print(f"Model: {params.get('model')}")
64+
print(f"Tools included: {'tools' in params}")
65+
print(f"Tool choice included: {'tool_choice' in params}")
66+
67+
# Test _build_messages
68+
messages, original = llm_instance._build_messages(
69+
prompt="What's the weather in Tokyo?",
70+
system_prompt="You are a helpful assistant",
71+
tools=test_tools
72+
)
73+
74+
print("\n=== System Message ===")
75+
for msg in messages:
76+
if msg['role'] == 'system':
77+
print(msg['content'])
78+
break
79+
80+
print("\n✅ Test completed successfully!")
81+
print("Key improvements:")
82+
print("- Tools parameter is removed for XML format models")
83+
print("- Tool descriptions are added to system prompt")
84+
print("- XML tool call format instructions are included")
85+
86+
if __name__ == "__main__":
87+
main()

0 commit comments

Comments
 (0)