fix(streaming): tighten tool limit terminal guards

This commit is contained in:
Frank Song committed 2026-06-12 10:03:31 +08:00
1 parent 08ba5a6676
commit 136568fe81
4 files changed
+43 -6

No files matched your search

+1 -1
View File
@@ -86,7 +86,7 @@ def _terminal_state_for_event(event_name: str, payload) -> str | None:
if name == "done" or name == "stream_end":
if isinstance(payload, dict):
explicit_state = str(payload.get("terminal_state") or "").strip().lower()
if explicit_state:
if explicit_state in {"tool_limit_reached"}:
return explicit_state
return "completed"
if name == "cancel":
+2 -2
View File
@@ -994,7 +994,7 @@ def _agent_result_tool_limit_reached(result) -> bool:
if (
'max_iterations_reached' in haystack
or 'maximum number of tool-calling iterations' in haystack
or 'tool-calling iterations' in haystack and 'maximum' in haystack
or ('tool-calling iterations' in haystack and 'maximum' in haystack)
):
return True
return False
@@ -6796,7 +6796,7 @@ def _run_agent_streaming(
# workspace-aware helper from this branch while still
# preserving the pre-turn length for downstream self-heal
# checks introduced on master.
_all_result_messages = _result_messages or []
_all_result_messages = result.get('messages') or []
_prev_len = len(_previous_context_messages)
_assistant_added = _assistant_reply_added_after_current_turn(
_all_result_messages,
+2
View File
@@ -110,6 +110,7 @@ def test_terminal_state_classification_distinguishes_crash_from_user_cancel(tmp_
append_run_event("session_1", "run_failed", "apperror", {"type": "auth_mismatch"}, session_dir=tmp_path)
append_run_event("session_1", "run_tool_limit", "apperror", {"type": "tool_limit_reached"}, session_dir=tmp_path)
append_run_event("session_1", "run_tool_limit_done", "done", {"terminal_state": "tool_limit_reached"}, session_dir=tmp_path)
append_run_event("session_1", "run_unknown_done", "done", {"terminal_state": "future_unknown_state"}, session_dir=tmp_path)
append_run_event("session_1", "run_done", "done", {"session": {}}, session_dir=tmp_path)
assert latest_run_summary("session_1", "run_cancelled", session_dir=tmp_path)["terminal_state"] == "interrupted-by-user"
@@ -117,6 +118,7 @@ def test_terminal_state_classification_distinguishes_crash_from_user_cancel(tmp_
assert latest_run_summary("session_1", "run_failed", session_dir=tmp_path)["terminal_state"] == "errored"
assert latest_run_summary("session_1", "run_tool_limit", session_dir=tmp_path)["terminal_state"] == "tool_limit_reached"
assert latest_run_summary("session_1", "run_tool_limit_done", session_dir=tmp_path)["terminal_state"] == "tool_limit_reached"
assert latest_run_summary("session_1", "run_unknown_done", session_dir=tmp_path)["terminal_state"] == "completed"
assert latest_run_summary("session_1", "run_done", session_dir=tmp_path)["terminal_state"] == "completed"
+38 -3
View File
@@ -12,7 +12,14 @@ from api.models import Session
ROOT = Path(__file__).resolve().parents[1]
def _run_streaming_with_fake_agent(tmp_path, monkeypatch, agent_result):
def _run_streaming_with_fake_agent(
tmp_path,
monkeypatch,
agent_result,
*,
prior_messages=None,
prior_context_messages=None,
):
session_dir = tmp_path / "sessions"
session_dir.mkdir()
monkeypatch.setattr(models, "SESSION_DIR", session_dir)
@@ -38,8 +45,8 @@ def _run_streaming_with_fake_agent(tmp_path, monkeypatch, agent_result):
title="Tool limit test",
workspace=str(tmp_path),
model="gpt-4o",
messages=[],
context_messages=[],
messages=list(prior_messages or []),
context_messages=list(prior_context_messages or []),
)
session.active_stream_id = stream_id
session.pending_user_message = "Do the long task."
@@ -124,6 +131,12 @@ def test_synthetic_max_iteration_summary_request_is_dropped_from_agent_result():
assert "here is the summary" in cleaned[-1]["content"]
def test_tool_limit_detection_uses_explicit_boolean_grouping():
streaming_py = (ROOT / "api" / "streaming.py").read_text(encoding="utf-8")
assert "or ('tool-calling iterations' in haystack and 'maximum' in haystack)" in streaming_py
def test_historical_synthetic_summary_prompt_does_not_mark_normal_result_as_tool_limit():
result = {
"messages": [
@@ -285,3 +298,25 @@ def test_streaming_historical_synthetic_prompt_normal_result_does_not_emit_tool_
assert "terminal_state" not in done_payloads[-1]
assert payload["messages"][-1]["role"] == "assistant"
assert payload["messages"][-1]["content"] == "Current task completed normally."
def test_streaming_empty_result_messages_do_not_treat_prior_assistant_as_current_answer(tmp_path, monkeypatch):
prior = [
{"role": "user", "content": "Earlier task."},
{"role": "assistant", "content": "Earlier answer."},
]
result = {"messages": []}
events, payload = _run_streaming_with_fake_agent(
tmp_path,
monkeypatch,
result,
prior_messages=prior,
prior_context_messages=prior,
)
apperror_payloads = [payload for event, payload in events if event == "apperror"]
assert apperror_payloads, "expected silent-failure apperror"
assert apperror_payloads[-1]["type"] == "no_response"
assert not [payload for event, payload in events if event == "done"]
assert payload["messages"][-1]["_error"] is True