feat: Add tool call parsing for 'tool: {...}' text format

- Detect 'tool: {...}' pattern in Google model text responses
- Parse and convert to OpenAI-compatible tool_calls format
- Extract assistant text from 'assistant: [...]' format if present
- Handle both 'action' and 'name' fields for tool identification
- Convert arguments to JSON string for OpenAI compatibility

This fixes issues where models return tool calls as text instead of
using proper function_call attributes.
parent e1e0092d
...@@ -451,6 +451,57 @@ class GoogleProviderHandler(BaseProviderHandler): ...@@ -451,6 +451,57 @@ class GoogleProviderHandler(BaseProviderHandler):
# Some models return tool calls as text content instead of using function_call attribute # Some models return tool calls as text content instead of using function_call attribute
if response_text and not openai_tool_calls: if response_text and not openai_tool_calls:
import json import json
import re
# First, check for "tool: {...}" format
tool_pattern = r'tool:\s*(\{[^}]*\})'
tool_match = re.search(tool_pattern, response_text, re.DOTALL)
if tool_match:
try:
tool_json_str = tool_match.group(1)
parsed_json = json.loads(tool_json_str)
logging.info(f"Detected 'tool:' format in text content: {parsed_json}")
# Convert to OpenAI tool_calls format
openai_tool_call = {
"id": f"call_{call_id}",
"type": "function",
"function": {
"name": parsed_json.get('action', parsed_json.get('name', 'unknown')),
"arguments": json.dumps({k: v for k, v in parsed_json.items() if k not in ['action', 'name']})
}
}
openai_tool_calls.append(openai_tool_call)
call_id += 1
logging.info(f"Converted 'tool:' format to OpenAI tool_calls: {openai_tool_call}")
# Extract any assistant text after the tool call
assistant_pattern = r"assistant:\s*(\[.*\])"
assistant_match = re.search(assistant_pattern, response_text, re.DOTALL)
if assistant_match:
try:
assistant_content = json.loads(assistant_match.group(1))
# Extract text from the assistant content
if isinstance(assistant_content, list) and len(assistant_content) > 0:
for item in assistant_content:
if isinstance(item, dict) and item.get('type') == 'text':
response_text = item.get('text', '')
break
else:
response_text = ""
else:
response_text = ""
except json.JSONDecodeError:
response_text = ""
else:
# Clear response_text since we're using tool_calls instead
response_text = ""
except (json.JSONDecodeError, Exception) as e:
logging.debug(f"Failed to parse 'tool:' format: {e}")
# Fall back to original JSON parsing if no tool: format found
elif not openai_tool_calls:
try: try:
# Try to parse as JSON # Try to parse as JSON
parsed_json = json.loads(response_text.strip()) parsed_json = json.loads(response_text.strip())
...@@ -466,7 +517,7 @@ class GoogleProviderHandler(BaseProviderHandler): ...@@ -466,7 +517,7 @@ class GoogleProviderHandler(BaseProviderHandler):
"type": "function", "type": "function",
"function": { "function": {
"name": parsed_json.get('action', 'unknown'), "name": parsed_json.get('action', 'unknown'),
"arguments": {k: v for k, v in parsed_json.items() if k != 'action'} "arguments": json.dumps({k: v for k, v in parsed_json.items() if k != 'action'})
} }
} }
openai_tool_calls.append(openai_tool_call) openai_tool_calls.append(openai_tool_call)
...@@ -481,7 +532,7 @@ class GoogleProviderHandler(BaseProviderHandler): ...@@ -481,7 +532,7 @@ class GoogleProviderHandler(BaseProviderHandler):
"type": "function", "type": "function",
"function": { "function": {
"name": parsed_json.get('name', parsed_json.get('function', 'unknown')), "name": parsed_json.get('name', parsed_json.get('function', 'unknown')),
"arguments": parsed_json.get('arguments', parsed_json.get('parameters', {})) "arguments": json.dumps(parsed_json.get('arguments', parsed_json.get('parameters', {})))
} }
} }
openai_tool_calls.append(openai_tool_call) openai_tool_calls.append(openai_tool_call)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment