fix: normalize DeepSeek non-standard tool_calls format

Systematic debugging revealed DeepSeek returns tool_calls in non-standard
format that bypasses LangChain's parse_tool_call():

**Root Cause:**
- OpenAI standard: {function: {name, arguments}, id}
- DeepSeek format: {name, args, id}
- LangChain's parse_tool_call() returns None when no 'function' key
- Result: Raw tool_call with string args → Pydantic validation error

**Solution:**
- ToolCallArgsParsingWrapper detects non-standard format
- Normalizes to OpenAI standard before LangChain processing
- Converts {name, args, id} → {function: {name, arguments}, id}
- Added diagnostic logging to identify format variations

**Impact:**
- DeepSeek models now work via OpenRouter
- No breaking changes to other providers (defensive design)
- Diagnostic logs help debug future format issues

Fixes validation errors:
  tool_calls.0.args: Input should be a valid dictionary
  [type=dict_type, input_value='{"symbol": "GILD", ...}', input_type=str]
This commit is contained in:
2025-11-06 17:51:33 -05:00
parent d199b093c1
commit b73d88ca8f

View File

@@ -32,9 +32,9 @@ class ToolCallArgsParsingWrapper:
# Model doesn't have this method (e.g., MockChatModel), skip patching # Model doesn't have this method (e.g., MockChatModel), skip patching
return return
# CRITICAL: Also patch parse_tool_call to see what it's returning # CRITICAL: Patch parse_tool_call in base.py's namespace (not in openai_tools module!)
from langchain_core.output_parsers import openai_tools from langchain_openai.chat_models import base as langchain_base
original_parse_tool_call = openai_tools.parse_tool_call original_parse_tool_call = langchain_base.parse_tool_call
def patched_parse_tool_call(raw_tool_call, *, partial=False, strict=False, return_id=True): def patched_parse_tool_call(raw_tool_call, *, partial=False, strict=False, return_id=True):
"""Patched parse_tool_call to log what it returns""" """Patched parse_tool_call to log what it returns"""
@@ -46,8 +46,8 @@ class ToolCallArgsParsingWrapper:
print(f"[DIAGNOSTIC] ⚠️ BUG FOUND! parse_tool_call returned STRING args: {result['args']}") print(f"[DIAGNOSTIC] ⚠️ BUG FOUND! parse_tool_call returned STRING args: {result['args']}")
return result return result
# Replace globally # Replace in base.py's namespace (where _convert_dict_to_message uses it)
openai_tools.parse_tool_call = patched_parse_tool_call langchain_base.parse_tool_call = patched_parse_tool_call
original_create_chat_result = self.wrapped_model._create_chat_result original_create_chat_result = self.wrapped_model._create_chat_result