Merge pull request #6097 from nesquena/stage/6081-noresp
Release exp: suppress false no-response after tool-limit turn with a final answer (#6081)
This commit is contained in:
3 files changed
+18
-10
No files matched your search
@@ -67,6 +67,8 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- **No more spurious "No response from provider" after a tool-limit turn that did answer.** When a turn hit the per-turn tool-iteration limit (`max_iterations_reached`) but the model had already produced a valid final answer, the settlement classifier still appended a "No response from provider" error card after the good reply — a regression of the earlier false-`no_response` fix, because the suppression path admitted only `_soft_partial_terminal_failure` and deliberately excluded `_tool_limit_reached`. The suppression now also covers `_tool_limit_reached`, but *only* when the transcript already holds a final answer; a tool-limit turn with no final answer still fails honestly, and explicit errors / cancellations / interruptions stay outside the suppression. Thanks @coyaSONG. (#6081, #6079)
|
||||
|
||||
- **New conversations can no longer become unstartable seconds after you create them.** On installs with a saved browser/password-manager credential and more than a few hundred sessions, the manager would autofill the account name into the sidebar conversation filter, silently firing a content search that walked every session through the cache and evicted the brand-new, not-yet-persisted session (its only copy lived in the in-memory cache) — so `/api/session/draft` and `/api/chat/start` then 404'd and the chat could never be started. Two fixes: the search/filter inputs are now `type="search"` with per-manager opt-out attributes and the Settings + onboarding password fields get a real `<form>` owner (`display:contents`, layout unchanged) so managers stop treating the sidebar filter as a username field; and `_session_is_evictable()` now proves persistence before evicting even a zero-message session, so an unsaved session you're still composing stays resident. Thanks @ai-ag2026. (#6083, #4765)
|
||||
|
||||
- **Live reasoning no longer drops or double-renders when the turn anchor defers a paint.** On the streaming reasoning render, when the assistant-turn anchor accepted a reasoning event but deferred actually painting it, the reasoning text could go missing — and earlier attempts to fix that could briefly show the same reasoning under two owners or, with multiple reasoning segments, update the wrong row. The fallback now matches the exact anchor row identity (`run_id:seq`), so the right segment updates in place, other rows are untouched, and no duplicate owner appears — across Transparent, Compact, and hide-all modes. Thanks @franksong2702. (#6048, #5720)
|
||||
|
||||
+1
-1
@@ -8891,7 +8891,7 @@ def _run_agent_streaming(
|
||||
)
|
||||
if (
|
||||
_terminal_failure
|
||||
and _soft_partial_terminal_failure
|
||||
and (_soft_partial_terminal_failure or _tool_limit_reached)
|
||||
and _classification['type'] == 'no_response'
|
||||
and not _saved_transcript_lacks_final_answer
|
||||
):
|
||||
|
||||
@@ -255,8 +255,9 @@ def test_streaming_tool_limit_with_final_answer_persists_clean_done_state(tmp_pa
|
||||
assert assistant["_statusCard"]["title"] == "Tool iteration limit reached"
|
||||
|
||||
|
||||
def test_streaming_tool_limit_without_final_answer_emits_no_final_apperror(tmp_path, monkeypatch):
|
||||
def test_streaming_tool_limit_partial_without_final_answer_emits_no_final_apperror(tmp_path, monkeypatch):
|
||||
result = {
|
||||
"status": "partial",
|
||||
"turn_exit_reason": "max_iterations_reached(30/30)",
|
||||
"messages": [
|
||||
{"role": "user", "content": "Do the long task."},
|
||||
@@ -406,7 +407,10 @@ def test_maybe_inject_max_iteration_summary_fallback_skips_when_no_fallback():
|
||||
assert out == messages
|
||||
|
||||
|
||||
def test_streaming_tool_limit_terminal_failure_does_not_mark_final_answer(tmp_path, monkeypatch):
|
||||
def test_streaming_tool_limit_partial_with_final_answer_suppresses_false_no_response(
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
):
|
||||
result = {
|
||||
"status": "partial",
|
||||
"turn_exit_reason": "max_iterations_reached(30/30)",
|
||||
@@ -418,19 +422,21 @@ def test_streaming_tool_limit_terminal_failure_does_not_mark_final_answer(tmp_pa
|
||||
|
||||
events, payload = _run_streaming_with_fake_agent(tmp_path, monkeypatch, result)
|
||||
|
||||
apperror_payloads = [payload for event, payload in events if event == "apperror"]
|
||||
assert apperror_payloads, "expected terminal-failure apperror"
|
||||
assert apperror_payloads[-1]["type"] == "tool_limit_reached"
|
||||
assert not [payload for event, payload in events if event == "done"]
|
||||
assert not [event_payload for event, event_payload in events if event == "apperror"]
|
||||
done_payloads = [event_payload for event, event_payload in events if event == "done"]
|
||||
assert done_payloads, "expected done SSE payload"
|
||||
assert done_payloads[-1]["terminal_state"] == "tool_limit_reached"
|
||||
assert done_payloads[-1]["terminal_reason"] == "max_iterations"
|
||||
assistant = next(
|
||||
message
|
||||
for message in payload["messages"]
|
||||
if message.get("role") == "assistant"
|
||||
and message.get("content") == "I reached the limit; here is the summary."
|
||||
)
|
||||
assert "_terminal_state" not in assistant
|
||||
assert "_statusCard" not in assistant
|
||||
assert payload["messages"][-1]["_error"] is True
|
||||
assert assistant["_terminal_state"] == "tool_limit_reached"
|
||||
assert assistant["_terminal_reason"] == "max_iterations"
|
||||
assert assistant["_statusCard"]["title"] == "Tool iteration limit reached"
|
||||
assert payload["messages"][-1] is assistant
|
||||
|
||||
|
||||
def test_streaming_historical_synthetic_prompt_normal_result_does_not_emit_tool_limit(tmp_path, monkeypatch):
|
||||
|
||||
Reference in new issue
Block a user