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,