Merge pull request #1058 from NousResearch/hermes/hermes-465f3702
fix: strip call_id/response_item_id from tool_calls for Mistral compatibility
This commit is contained in:
commit
73ea5102dc
1 changed files with 37 additions and 0 deletions
37
run_agent.py
37
run_agent.py
|
|
@ -2543,6 +2543,31 @@ class AIAgent:
|
|||
|
||||
return msg
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_tool_calls_for_strict_api(api_msg: dict) -> dict:
|
||||
"""Strip Codex Responses API fields from tool_calls for strict providers.
|
||||
|
||||
Providers like Mistral strictly validate the Chat Completions schema
|
||||
and reject unknown fields (call_id, response_item_id) with 422.
|
||||
These fields are preserved in the internal message history — this
|
||||
method only modifies the outgoing API copy.
|
||||
|
||||
Creates new tool_call dicts rather than mutating in-place, so the
|
||||
original messages list retains call_id/response_item_id for Codex
|
||||
Responses API compatibility (e.g. if the session falls back to a
|
||||
Codex provider later).
|
||||
"""
|
||||
tool_calls = api_msg.get("tool_calls")
|
||||
if not isinstance(tool_calls, list):
|
||||
return api_msg
|
||||
_STRIP_KEYS = {"call_id", "response_item_id"}
|
||||
api_msg["tool_calls"] = [
|
||||
{k: v for k, v in tc.items() if k not in _STRIP_KEYS}
|
||||
if isinstance(tc, dict) else tc
|
||||
for tc in tool_calls
|
||||
]
|
||||
return api_msg
|
||||
|
||||
def flush_memories(self, messages: list = None, min_turns: int = None):
|
||||
"""Give the model one turn to persist memories before context is lost.
|
||||
|
||||
|
|
@ -2580,6 +2605,7 @@ class AIAgent:
|
|||
|
||||
try:
|
||||
# Build API messages for the flush call
|
||||
_is_strict_api = "api.mistral.ai" in self.base_url.lower()
|
||||
api_messages = []
|
||||
for msg in messages:
|
||||
api_msg = msg.copy()
|
||||
|
|
@ -2590,6 +2616,8 @@ class AIAgent:
|
|||
api_msg.pop("reasoning", None)
|
||||
api_msg.pop("finish_reason", None)
|
||||
api_msg.pop("_flush_sentinel", None)
|
||||
if _is_strict_api:
|
||||
self._sanitize_tool_calls_for_strict_api(api_msg)
|
||||
api_messages.append(api_msg)
|
||||
|
||||
if self._cached_system_prompt:
|
||||
|
|
@ -3055,11 +3083,14 @@ class AIAgent:
|
|||
try:
|
||||
# Build API messages, stripping internal-only fields
|
||||
# (finish_reason, reasoning) that strict APIs like Mistral reject with 422
|
||||
_is_strict_api = "api.mistral.ai" in self.base_url.lower()
|
||||
api_messages = []
|
||||
for msg in messages:
|
||||
api_msg = msg.copy()
|
||||
for internal_field in ("reasoning", "finish_reason"):
|
||||
api_msg.pop(internal_field, None)
|
||||
if _is_strict_api:
|
||||
self._sanitize_tool_calls_for_strict_api(api_msg)
|
||||
api_messages.append(api_msg)
|
||||
|
||||
effective_system = self._cached_system_prompt or ""
|
||||
|
|
@ -3438,6 +3469,12 @@ class AIAgent:
|
|||
# Remove finish_reason - not accepted by strict APIs (e.g. Mistral)
|
||||
if "finish_reason" in api_msg:
|
||||
api_msg.pop("finish_reason")
|
||||
# Strip Codex Responses API fields (call_id, response_item_id) for
|
||||
# strict providers like Mistral that reject unknown fields with 422.
|
||||
# Uses new dicts so the internal messages list retains the fields
|
||||
# for Codex Responses compatibility.
|
||||
if "api.mistral.ai" in self.base_url.lower():
|
||||
self._sanitize_tool_calls_for_strict_api(api_msg)
|
||||
# Keep 'reasoning_details' - OpenRouter uses this for multi-turn reasoning context
|
||||
# The signature field helps maintain reasoning continuity
|
||||
api_messages.append(api_msg)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue