fix(streaming): tighten tool limit terminal guards
This commit is contained in:
1 parent
08ba5a6676
commit
136568fe81
4 files changed
+43
-6
No files matched your search
+1
-1
@@ -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
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user