fix: handle tool_calls as single object or array
- Parse tool_calls whether it's a single object {...} or array [...]
- Normalize to list for consistent processing
- Add debug logging to trace tool execution flow
- Fix variable name (value_str instead of array_str)
This commit is contained in:
+27
-7
@@ -139,19 +139,25 @@ def parse_tool_calls(text: str) -> tuple:
|
||||
cleaned_text = re.sub(r'```(?:json)?\s*\n?(.+?)```', r'\1', cleaned_text, flags=re.DOTALL)
|
||||
cleaned_text = cleaned_text.strip()
|
||||
|
||||
# Try to find JSON with tool_calls - look for { tool_calls: [...] } pattern
|
||||
# Try to find JSON with tool_calls - look for { tool_calls: [...] } or { tool_calls: {...} } pattern
|
||||
try:
|
||||
# Look for tool_calls inside braces (handle both quoted and unquoted keys)
|
||||
pattern = r'\{\s*"?tool_calls"?\s*:\s*(\[.*?\])\s*\}'
|
||||
# Match either an array \[...\] or a single object {...}
|
||||
pattern = r'\{\s*"?tool_calls"?\s*:\s*(\[.*?\]|\{.*?\})\s*\}'
|
||||
match = re.search(pattern, cleaned_text, re.DOTALL)
|
||||
if match:
|
||||
array_str = match.group(1)
|
||||
value_str = match.group(1)
|
||||
# Try to parse as JSON first
|
||||
try:
|
||||
tool_calls = json.loads(array_str)
|
||||
parsed = json.loads(value_str)
|
||||
# Normalize to list: if it's a dict (single tool), wrap in list
|
||||
if isinstance(parsed, dict):
|
||||
tool_calls = [parsed]
|
||||
else:
|
||||
tool_calls = parsed
|
||||
except json.JSONDecodeError:
|
||||
# Fix common JSON issues in model output
|
||||
fixed = array_str
|
||||
fixed = value_str
|
||||
# Step 1: Handle unquoted keys (JavaScript style)
|
||||
fixed = re.sub(r'([{,])\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:', r'\1"\2":', fixed)
|
||||
|
||||
@@ -181,7 +187,12 @@ def parse_tool_calls(text: str) -> tuple:
|
||||
fixed = fixed.replace("'", '"')
|
||||
|
||||
try:
|
||||
tool_calls = json.loads(fixed)
|
||||
parsed = json.loads(fixed)
|
||||
# Normalize to list
|
||||
if isinstance(parsed, dict):
|
||||
tool_calls = [parsed]
|
||||
else:
|
||||
tool_calls = parsed
|
||||
except json.JSONDecodeError as e2:
|
||||
# If still fails, try one more approach - manual extraction
|
||||
try:
|
||||
@@ -190,7 +201,7 @@ def parse_tool_calls(text: str) -> tuple:
|
||||
# Find all function blocks - need to handle nested braces
|
||||
# Look for "function": {...} where ... can contain nested braces
|
||||
func_pattern = r'"function":\s*(\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\})'
|
||||
func_matches = list(re.finditer(func_pattern, array_str, re.DOTALL))
|
||||
func_matches = list(re.finditer(func_pattern, value_str, re.DOTALL))
|
||||
|
||||
for i, func_match in enumerate(func_matches):
|
||||
func_content = func_match.group(1)
|
||||
@@ -367,6 +378,7 @@ async def chat_completions(request: ChatCompletionRequest):
|
||||
# Format messages into prompt (with tools if provided)
|
||||
prompt = format_messages_with_tools(request.messages, request.tools)
|
||||
has_tools = request.tools is not None and len(request.tools) > 0
|
||||
print(f"DEBUG: has_tools={has_tools}, tools_count={len(request.tools) if request.tools else 0}")
|
||||
|
||||
# Generate ID
|
||||
completion_id = f"chatcmpl-{uuid.uuid4().hex[:12]}"
|
||||
@@ -507,6 +519,8 @@ async def chat_completions(request: ChatCompletionRequest):
|
||||
|
||||
response_text = result.selected_response.text
|
||||
tokens_generated = result.selected_response.tokens_generated
|
||||
print(f"DEBUG: Generated response (tokens={tokens_generated})")
|
||||
print(f"DEBUG: Response preview: {response_text[:200]}...")
|
||||
|
||||
# Parse tool calls if tools were provided
|
||||
content = response_text
|
||||
@@ -514,7 +528,9 @@ async def chat_completions(request: ChatCompletionRequest):
|
||||
finish_reason = "stop"
|
||||
|
||||
if has_tools:
|
||||
print(f"DEBUG: Parsing tool calls from response...")
|
||||
content, tool_calls_parsed = parse_tool_calls(response_text)
|
||||
print(f"DEBUG: parse_tool_calls returned: content_len={len(content)}, parsed={tool_calls_parsed is not None}")
|
||||
if tool_calls_parsed:
|
||||
print(f" 🔧 Model requesting {len(tool_calls_parsed)} tool(s)...")
|
||||
executor = get_tool_executor()
|
||||
@@ -543,6 +559,10 @@ async def chat_completions(request: ChatCompletionRequest):
|
||||
finish_reason = "stop"
|
||||
tool_calls = [] # Clear tool_calls since we executed them
|
||||
print(f" ✅ All tools executed, returning results")
|
||||
else:
|
||||
print(f"DEBUG: No tool calls parsed from response")
|
||||
else:
|
||||
print(f"DEBUG: No tools requested, returning normal response")
|
||||
|
||||
# Estimate prompt tokens (rough approximation)
|
||||
prompt_tokens = len(prompt) // 4
|
||||
|
||||
Reference in New Issue
Block a user