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
|
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):
|
def flush_memories(self, messages: list = None, min_turns: int = None):
|
||||||
"""Give the model one turn to persist memories before context is lost.
|
"""Give the model one turn to persist memories before context is lost.
|
||||||
|
|
||||||
|
|
@ -2580,6 +2605,7 @@ class AIAgent:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Build API messages for the flush call
|
# Build API messages for the flush call
|
||||||
|
_is_strict_api = "api.mistral.ai" in self.base_url.lower()
|
||||||
api_messages = []
|
api_messages = []
|
||||||
for msg in messages:
|
for msg in messages:
|
||||||
api_msg = msg.copy()
|
api_msg = msg.copy()
|
||||||
|
|
@ -2590,6 +2616,8 @@ class AIAgent:
|
||||||
api_msg.pop("reasoning", None)
|
api_msg.pop("reasoning", None)
|
||||||
api_msg.pop("finish_reason", None)
|
api_msg.pop("finish_reason", None)
|
||||||
api_msg.pop("_flush_sentinel", 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)
|
api_messages.append(api_msg)
|
||||||
|
|
||||||
if self._cached_system_prompt:
|
if self._cached_system_prompt:
|
||||||
|
|
@ -3055,11 +3083,14 @@ class AIAgent:
|
||||||
try:
|
try:
|
||||||
# Build API messages, stripping internal-only fields
|
# Build API messages, stripping internal-only fields
|
||||||
# (finish_reason, reasoning) that strict APIs like Mistral reject with 422
|
# (finish_reason, reasoning) that strict APIs like Mistral reject with 422
|
||||||
|
_is_strict_api = "api.mistral.ai" in self.base_url.lower()
|
||||||
api_messages = []
|
api_messages = []
|
||||||
for msg in messages:
|
for msg in messages:
|
||||||
api_msg = msg.copy()
|
api_msg = msg.copy()
|
||||||
for internal_field in ("reasoning", "finish_reason"):
|
for internal_field in ("reasoning", "finish_reason"):
|
||||||
api_msg.pop(internal_field, None)
|
api_msg.pop(internal_field, None)
|
||||||
|
if _is_strict_api:
|
||||||
|
self._sanitize_tool_calls_for_strict_api(api_msg)
|
||||||
api_messages.append(api_msg)
|
api_messages.append(api_msg)
|
||||||
|
|
||||||
effective_system = self._cached_system_prompt or ""
|
effective_system = self._cached_system_prompt or ""
|
||||||
|
|
@ -3438,6 +3469,12 @@ class AIAgent:
|
||||||
# Remove finish_reason - not accepted by strict APIs (e.g. Mistral)
|
# Remove finish_reason - not accepted by strict APIs (e.g. Mistral)
|
||||||
if "finish_reason" in api_msg:
|
if "finish_reason" in api_msg:
|
||||||
api_msg.pop("finish_reason")
|
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
|
# Keep 'reasoning_details' - OpenRouter uses this for multi-turn reasoning context
|
||||||
# The signature field helps maintain reasoning continuity
|
# The signature field helps maintain reasoning continuity
|
||||||
api_messages.append(api_msg)
|
api_messages.append(api_msg)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue