From afa50b0ff7d27592b7e770e25a80d7685b8b65be Mon Sep 17 00:00:00 2001 From: Jon Saad-Falcon <41205309+jonsaadfalcon@users.noreply.github.com> Date: Sun, 15 Mar 2026 12:12:20 -0700 Subject: [PATCH] fix: parse tool_call arguments as dicts for OpenAI-compatible engines (#69) messages_to_dicts() serializes tool_call arguments as JSON strings, but OpenAI-compatible servers (vLLM, SGLang, llama.cpp, etc.) expect them as JSON objects. This caused 400 Bad Request errors on every multi-turn tool-calling conversation, breaking GAIA, DeepPlanning, and LifelongAgent benchmarks for all local models. The fix adds _fix_tool_call_arguments() to _OpenAICompatibleEngine which json.loads() any string-typed arguments back to dicts before sending. Applied to both generate() and stream() methods. This is the same fix as commit 45c0e44 (Ollama), now applied to all OpenAI-compatible engines. Co-authored-by: Claude Opus 4.6 (1M context) --- src/openjarvis/engine/_openai_compat.py | 26 +++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/openjarvis/engine/_openai_compat.py b/src/openjarvis/engine/_openai_compat.py index 447cd2de..fcc08034 100644 --- a/src/openjarvis/engine/_openai_compat.py +++ b/src/openjarvis/engine/_openai_compat.py @@ -32,6 +32,26 @@ class _OpenAICompatibleEngine(InferenceEngine): # -- InferenceEngine interface ------------------------------------------ + @staticmethod + def _fix_tool_call_arguments(msg_dicts: list) -> list: + """Ensure tool_call arguments are dicts, not JSON strings. + + OpenAI-compatible servers (vLLM, SGLang, llama.cpp, etc.) expect + tool_call arguments as JSON objects. ``messages_to_dicts`` may + serialize them as strings, which causes 400 errors on multi-turn + tool-calling conversations. + """ + for md in msg_dicts: + for tc in md.get("tool_calls", []): + fn = tc.get("function", {}) + args = fn.get("arguments") + if isinstance(args, str): + try: + fn["arguments"] = json.loads(args) + except (json.JSONDecodeError, TypeError): + pass + return msg_dicts + def generate( self, messages: Sequence[Message], @@ -41,9 +61,10 @@ class _OpenAICompatibleEngine(InferenceEngine): max_tokens: int = 1024, **kwargs: Any, ) -> Dict[str, Any]: + msg_dicts = self._fix_tool_call_arguments(messages_to_dicts(messages)) payload: Dict[str, Any] = { "model": model, - "messages": messages_to_dicts(messages), + "messages": msg_dicts, "temperature": temperature, "max_tokens": max_tokens, "stream": False, @@ -105,9 +126,10 @@ class _OpenAICompatibleEngine(InferenceEngine): max_tokens: int = 1024, **kwargs: Any, ) -> AsyncIterator[str]: + msg_dicts = self._fix_tool_call_arguments(messages_to_dicts(messages)) payload: Dict[str, Any] = { "model": model, - "messages": messages_to_dicts(messages), + "messages": msg_dicts, "temperature": temperature, "max_tokens": max_tokens, "stream": True,