hermes: sanitize classifier tool history
This commit is contained in:
parent
7dbde8e85d
commit
c7dc81c0c3
@ -79,18 +79,20 @@ def _compact_message(message: dict[str, Any], limit: int) -> dict[str, Any]:
|
|||||||
result = copy.deepcopy(message)
|
result = copy.deepcopy(message)
|
||||||
if "content" in result:
|
if "content" in result:
|
||||||
result["content"] = _compact_content(result["content"], limit)
|
result["content"] = _compact_content(result["content"], limit)
|
||||||
tool_calls = result.get("tool_calls")
|
# The local judge only classifies the current boundary. OpenAI-compatible
|
||||||
if isinstance(tool_calls, list):
|
# Ollama validates assistant tool-call JSON and tool/result pairing before
|
||||||
kept: list[dict[str, Any]] = []
|
# inference, so a bounded or selectively retained transcript can become an
|
||||||
for call in tool_calls[-4:]:
|
# invalid conversation even though its text is sufficient for routing.
|
||||||
if not isinstance(call, dict):
|
# Preserve tool evidence as plain user text and remove protocol metadata.
|
||||||
continue
|
had_tool_calls = bool(result.pop("tool_calls", None))
|
||||||
item = copy.deepcopy(call)
|
result.pop("tool_call_id", None)
|
||||||
function = item.get("function")
|
result.pop("name", None)
|
||||||
if isinstance(function, dict) and isinstance(function.get("arguments"), str):
|
if result.get("role") == "tool":
|
||||||
function["arguments"] = _bounded_text(function["arguments"], 600)
|
result["role"] = "user"
|
||||||
kept.append(item)
|
content = result.get("content")
|
||||||
result["tool_calls"] = kept
|
result["content"] = f"[tool evidence]\n{content or ''}"
|
||||||
|
elif had_tool_calls and not result.get("content"):
|
||||||
|
result["content"] = "[assistant requested an external tool]"
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@ -153,6 +155,8 @@ def compact_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
|||||||
# The judge never needs tools or binary inputs. Switchyard supplies a
|
# The judge never needs tools or binary inputs. Switchyard supplies a
|
||||||
# response schema separately, and that contract must remain untouched.
|
# response schema separately, and that contract must remain untouched.
|
||||||
result.pop("tools", None)
|
result.pop("tools", None)
|
||||||
|
result.pop("tool_choice", None)
|
||||||
|
result.pop("parallel_tool_calls", None)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1229,10 +1229,24 @@ def test_classifier_broker_bounds_history_without_losing_routing_intent(monkeypa
|
|||||||
"role": "user",
|
"role": "user",
|
||||||
"content": "Build and verify the Cassandra release safely.",
|
"content": "Build and verify the Cassandra release safely.",
|
||||||
},
|
},
|
||||||
{"role": "assistant", "content": "working"},
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": None,
|
||||||
|
"tool_calls": [
|
||||||
|
{
|
||||||
|
"id": "call_large",
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "large_tool",
|
||||||
|
"arguments": '{"command":"' + ("x" * 5000) + '"}',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"role": "tool",
|
"role": "tool",
|
||||||
"content": "unbounded test output " * 10000,
|
"content": "unbounded test output " * 10000,
|
||||||
|
"tool_call_id": "call_large",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
@ -1243,6 +1257,8 @@ def test_classifier_broker_bounds_history_without_losing_routing_intent(monkeypa
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"tools": [{"type": "function", "function": {"name": "large_tool"}}],
|
"tools": [{"type": "function", "function": {"name": "large_tool"}}],
|
||||||
|
"tool_choice": "auto",
|
||||||
|
"parallel_tool_calls": True,
|
||||||
"response_format": {"type": "json_object"},
|
"response_format": {"type": "json_object"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1252,6 +1268,13 @@ def test_classifier_broker_bounds_history_without_losing_routing_intent(monkeypa
|
|||||||
assert compacted["model"] == payload["model"]
|
assert compacted["model"] == payload["model"]
|
||||||
assert compacted["response_format"] == payload["response_format"]
|
assert compacted["response_format"] == payload["response_format"]
|
||||||
assert "tools" not in compacted
|
assert "tools" not in compacted
|
||||||
|
assert "tool_choice" not in compacted
|
||||||
|
assert "parallel_tool_calls" not in compacted
|
||||||
|
assert all(message.get("role") != "tool" for message in compacted["messages"])
|
||||||
|
assert all("tool_call_id" not in message for message in compacted["messages"])
|
||||||
|
assert all("tool_calls" not in message for message in compacted["messages"])
|
||||||
|
assert "[tool evidence]" in encoded
|
||||||
|
assert "[assistant requested an external tool]" in encoded
|
||||||
assert "Build and verify the Cassandra release safely." in encoded
|
assert "Build and verify the Cassandra release safely." in encoded
|
||||||
assert "Turn this cat into a cute clown." in encoded
|
assert "Turn this cat into a cute clown." in encoded
|
||||||
assert "image attachment available to the selected worker" in encoded
|
assert "image attachment available to the selected worker" in encoded
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user