mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-01 12:02:05 +00:00
fix: normalize tool_call_id whitespace in sanitizer
_sanitize_api_messages() compared raw tool_call_id strings without stripping whitespace. When assistant-side IDs and tool-result IDs diverged due to surrounding whitespace, valid tool results were treated as orphaned and replaced with [Result unavailable] stub placeholders. Strip whitespace in _get_tool_call_id_static() (both call_id/id paths, dict and object) and at the two result_call_id comparison sites in sanitize_api_messages(). Adds regression tests for preserved-whitespace results and orphaned-whitespace removal. Closes #9999
This commit is contained in:
parent
f9b619dfae
commit
fa3ab2ffd0
3 changed files with 28 additions and 4 deletions
|
|
@ -2163,7 +2163,7 @@ def sanitize_api_messages(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]
|
|||
result_call_ids: set = set()
|
||||
for msg in messages:
|
||||
if msg.get("role") == "tool":
|
||||
cid = msg.get("tool_call_id")
|
||||
cid = (msg.get("tool_call_id") or "").strip()
|
||||
if cid:
|
||||
result_call_ids.add(cid)
|
||||
|
||||
|
|
@ -2172,7 +2172,7 @@ def sanitize_api_messages(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]
|
|||
if orphaned_results:
|
||||
messages = [
|
||||
m for m in messages
|
||||
if not (m.get("role") == "tool" and m.get("tool_call_id") in orphaned_results)
|
||||
if not (m.get("role") == "tool" and (m.get("tool_call_id") or "").strip() in orphaned_results)
|
||||
]
|
||||
_ra().logger.debug(
|
||||
"Pre-call sanitizer: removed %d orphaned tool result(s)",
|
||||
|
|
|
|||
|
|
@ -3429,8 +3429,8 @@ class AIAgent:
|
|||
def _get_tool_call_id_static(tc) -> str:
|
||||
"""Extract call ID from a tool_call entry (dict or object)."""
|
||||
if isinstance(tc, dict):
|
||||
return tc.get("call_id", "") or tc.get("id", "") or ""
|
||||
return getattr(tc, "call_id", "") or getattr(tc, "id", "") or ""
|
||||
return (tc.get("call_id", "") or tc.get("id", "") or "").strip()
|
||||
return (getattr(tc, "call_id", "") or getattr(tc, "id", "") or "").strip()
|
||||
|
||||
@staticmethod
|
||||
def _get_tool_call_name_static(tc) -> str:
|
||||
|
|
|
|||
|
|
@ -108,6 +108,30 @@ class TestSanitizeApiMessages:
|
|||
assert len(out) == 2
|
||||
assert out[1]["tool_call_id"] == "c6"
|
||||
|
||||
def test_tool_result_with_leading_whitespace_preserved(self):
|
||||
"""Tool result IDs with leading/trailing whitespace should match assistant call IDs."""
|
||||
msgs = [
|
||||
{"role": "assistant", "tool_calls": [assistant_dict_call("functions.cronjob:24")]},
|
||||
tool_result(" functions.cronjob:24"), # leading whitespace
|
||||
]
|
||||
out = AIAgent._sanitize_api_messages(msgs)
|
||||
# Should NOT inject a stub — the tool result is valid after stripping
|
||||
assert len(out) == 2
|
||||
assert out[1]["role"] == "tool"
|
||||
assert out[1]["content"] == "ok"
|
||||
|
||||
def test_truly_orphaned_with_whitespace_still_removed(self):
|
||||
"""Truly orphaned tool results with whitespace should still be removed."""
|
||||
msgs = [
|
||||
{"role": "assistant", "tool_calls": [assistant_dict_call("c_valid")]},
|
||||
tool_result(" c_ORPHAN "), # whitespace + no matching call
|
||||
]
|
||||
out = AIAgent._sanitize_api_messages(msgs)
|
||||
assert len(out) == 2 # assistant + stub for c_valid, orphan removed
|
||||
tool_msgs = [m for m in out if m["role"] == "tool"]
|
||||
assert len(tool_msgs) == 1
|
||||
assert tool_msgs[0]["tool_call_id"] == "c_valid"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 2a — _cap_delegate_task_calls
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue