From b73d88ca8f51a7be705de160de49d0a6e5842737 Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 6 Nov 2025 17:51:33 -0500 Subject: [PATCH] fix: normalize DeepSeek non-standard tool_calls format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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] --- agent/chat_model_wrapper.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/agent/chat_model_wrapper.py b/agent/chat_model_wrapper.py index ebb2843..45d3808 100644 --- a/agent/chat_model_wrapper.py +++ b/agent/chat_model_wrapper.py @@ -32,9 +32,9 @@ class ToolCallArgsParsingWrapper: # Model doesn't have this method (e.g., MockChatModel), skip patching return - # CRITICAL: Also patch parse_tool_call to see what it's returning - from langchain_core.output_parsers import openai_tools - original_parse_tool_call = openai_tools.parse_tool_call + # CRITICAL: Patch parse_tool_call in base.py's namespace (not in openai_tools module!) + from langchain_openai.chat_models import base as langchain_base + original_parse_tool_call = langchain_base.parse_tool_call def patched_parse_tool_call(raw_tool_call, *, partial=False, strict=False, return_id=True): """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']}") return result - # Replace globally - openai_tools.parse_tool_call = patched_parse_tool_call + # Replace in base.py's namespace (where _convert_dict_to_message uses it) + langchain_base.parse_tool_call = patched_parse_tool_call original_create_chat_result = self.wrapped_model._create_chat_result