Commit 8677ba98 authored by Your Name's avatar Your Name

Add nested tool_call XML parsing to ToolCallParser and fix formatted_response None bug

- Added support for <tool_call><tool><name>...</name><arguments>...</arguments></tool></tool_call> format
- Added support for multiple tool calls in <tool_call> wrapper
- Added same patterns to ApexBig50Parser for direct handling
- Added exception handling for formatter.format_full() to prevent TypeError
- Added fallback response when formatted_response is None
- ToolCallParser now serves as fallback for all specific model parsers
parent 5568a3e7
......@@ -528,6 +528,36 @@ class ApexBig50Parser(BaseParser):
except:
pass
# NEW: <tool_call><tool><name>...</name><arguments>...</arguments></tool></tool_call> format
nested_tool_call_pattern = r'<tool_call>\s*<tool>\s*<name>(.*?)</name>\s*<arguments>(.*?)</arguments>\s*</tool>\s*</tool_call>'
for match in re.findall(nested_tool_call_pattern, text, re.DOTALL | re.IGNORECASE):
tool_name, args_content = match
tool_name = tool_name.strip()
if not tool_name:
continue
# Try to parse arguments as JSON
try:
args = json.loads(args_content.strip())
except:
# Fallback: treat as a simple string argument
args = args_content.strip()
results.append(self._to_oa(tool_name, args))
# NEW: Multiple tool calls in <tool_call> wrapper
multi_tool_pattern = r'<tool_call>\s*(<tool>.*?</tool>)\s*</tool_call>'
for tool_block in re.findall(multi_tool_pattern, text, re.DOTALL | re.IGNORECASE):
inner_pattern = r'<tool>\s*<name>(.*?)</name>\s*<arguments>(.*?)</arguments>\s*</tool>'
for inner_match in re.findall(inner_pattern, tool_block, re.DOTALL | re.IGNORECASE):
tool_name, args_content = inner_match
tool_name = tool_name.strip()
if not tool_name:
continue
try:
args = json.loads(args_content.strip())
except:
args = args_content.strip()
results.append(self._to_oa(tool_name, args))
# React pattern
react_matches = re.findall(r'Action:\s*(.*?)\nAction Input:\s*(\{.*?\})', text, re.DOTALL)
for name, args_raw in react_matches:
......@@ -949,6 +979,61 @@ class ToolCallParser:
}
})
# NEW: Pattern for <tool_call><tool><name>...</name><arguments>...</arguments></tool></tool_call>
# Example: <tool_call><tool><name>search</name><arguments>{"query": "test"}</arguments></tool></tool_call>
pattern_nested = r'<tool_call>\s*<tool>\s*<name>(.*?)</name>\s*<arguments>(.*?)</arguments>\s*</tool>\s*</tool_call>'
matches_nested = re.findall(pattern_nested, text, re.DOTALL | re.IGNORECASE)
for name, args_str in matches_nested:
name = name.strip()
if not name:
continue
# Try to parse arguments as JSON
try:
args = json.loads(args_str.strip()) if args_str.strip() else {}
except json.JSONDecodeError:
# If not valid JSON, treat as empty object
args = {}
tool_calls.append({
"id": f"call_{uuid.uuid4().hex[:16]}",
"type": "function",
"function": {
"name": name,
"arguments": json.dumps(args)
}
})
# NEW: Pattern for multiple tool calls in <tool_call> wrapper
# Example: <tool_call><tool>...</tool><tool>...</tool></tool_call>
pattern_multi = r'<tool_call>\s*(<tool>.*?</tool>)\s*</tool_call>'
matches_multi = re.findall(pattern_multi, text, re.DOTALL | re.IGNORECASE)
for tool_block in matches_multi:
# Extract individual tool calls from within the tool_call wrapper
inner_pattern = r'<tool>\s*<name>(.*?)</name>\s*<arguments>(.*?)</arguments>\s*</tool>'
inner_matches = re.findall(inner_pattern, tool_block, re.DOTALL | re.IGNORECASE)
for name, args_str in inner_matches:
name = name.strip()
if not name:
continue
try:
args = json.loads(args_str.strip()) if args_str.strip() else {}
except json.JSONDecodeError:
args = {}
tool_calls.append({
"id": f"call_{uuid.uuid4().hex[:16]}",
"type": "function",
"function": {
"name": name,
"arguments": json.dumps(args)
}
})
return tool_calls
def set_model_name(self, model_name: str):
......
......@@ -2449,18 +2449,23 @@ async def chat_completions(request: ChatCompletionRequest, http_request: Request
# Step 2: Use OpenAIFormatter for final formatting
formatter = OpenAIFormatter(response_model_name)
formatted_response = formatter.format_full(
text=clean_text,
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
tool_calls=extracted_tool_calls
)
try:
formatted_response = formatter.format_full(
text=clean_text,
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
tool_calls=extracted_tool_calls
)
except Exception as e:
print(f"RAW: ERROR in formatter.format_full: {e}")
formatted_response = None
if global_debug:
if formatted_response:
print(f"RAW: Passed through formatter, got: {formatted_response.get('choices', [{}])[0].get('message', {}).get('content', '')[:100]}...")
if formatted_response and isinstance(formatted_response, dict):
content = formatted_response.get('choices', [{}])[0].get('message', {}).get('content', '') if formatted_response.get('choices') else ''
print(f"RAW: Passed through formatter, got: {content[:100]}...")
else:
print(f"RAW: WARNING - formatter returned None!")
print(f"RAW: WARNING - formatter returned None or invalid response!")
# Add mock reasoning stats if 'mock' is in force_reasoning_args
# But only if we DON'T already have real reasoning from extraction
......@@ -2507,12 +2512,35 @@ async def chat_completions(request: ChatCompletionRequest, http_request: Request
# Add rate limit headers
headers = {}
if 'usage' in formatted_response:
if formatted_response and 'usage' in formatted_response:
headers = current_manager.backend.get_rate_limit_headers(
prompt_tokens=formatted_response.get('usage', {}).get('prompt_tokens', 0),
completion_tokens=formatted_response.get('usage', {}).get('completion_tokens', 0)
) if hasattr(current_manager.backend, 'get_rate_limit_headers') else {}
# Ensure we have a valid response to return
if not formatted_response:
# Create a minimal fallback response
formatted_response = {
"id": f"chatcmpl-{uuid.uuid4().hex}",
"object": "chat.completion",
"created": int(time.time()),
"model": response_model_name,
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": clean_text or ""
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": prompt_tokens,
"completion_tokens": completion_tokens,
"total_tokens": prompt_tokens + completion_tokens
}
}
return JSONResponse(content=formatted_response, headers=headers)
if request.stream:
......
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