mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-12 10:13:09 +00:00
fix(stream): persist a turn that errored without an answer as failed
WorkflowEngine reports node failures by *yielding* `{"type": "error"}`
rather than raising, so complete_stream's generator returns normally and
the except handler never runs. The turn was finalized `status="complete"`
with an empty response.
Live, the client renders an error bubble with a Retry button. On reload
it does not: mapServerQueryToClient only surfaces `metadata.error` for
`failed` rows, so history showed a blank message with no error and no way
to retry. A user hitting this re-sends the same prompt into new
conversations, which is exactly what the 2026-08-01 report shows — nine
blank first messages in seven hours.
Tracks a `stream_error` flag alongside the existing `paused` machinery
and finalizes `failed` when the turn produced no answer, recording the
user-facing message in `metadata.error`. An error arriving *after* output
keeps `complete` so partial text is not discarded; structured answers
count as output too, since they live in `structured_chunks` rather than
`response_full`. The flag is recorded before the pause branches so those
paths cannot lose it.
save_conversation grew a `status` parameter (default `complete`) for the
non-WAL branch, which took no status and so landed on the column default
— the same blank-complete row on a path the WAL fix did not cover.
Title generation now also runs for failed turns: _maybe_generate_title
only regenerates while the name is still the question-prefix fallback, so
skipping it would strand a conversation whose first turn failed with the
raw prompt as its name forever.
logging.py counts a yielded error toward `activity_finished.status`.
These failures previously logged `status=ok` with `answer_length=0`,
which is why user-visible blank answers never appeared in error metrics.
This commit is contained in:
1 parent
f5129a5656
commit
bda11242e1
6 files changed
+261
-5
No files matched your search
@@ -257,6 +257,35 @@ class TestLogActivity:
|
||||
assert finished.status == "error"
|
||||
assert finished.error_class == "ValueError"
|
||||
|
||||
def test_log_activity_records_error_status_on_yielded_error(self, caplog):
|
||||
# Workflow node failures are reported as a yielded ``type: error``
|
||||
# event rather than a raised exception, so the decorator's ``except``
|
||||
# never runs. Those turns used to log ``status="ok"`` with
|
||||
# ``answer_length=0``, which kept real user-visible failures out of
|
||||
# every error dashboard.
|
||||
import logging as _logging
|
||||
|
||||
from application.logging import log_activity
|
||||
|
||||
class FakeAgent:
|
||||
endpoint = "stream"
|
||||
user = "user1"
|
||||
user_api_key = ""
|
||||
query = "hello"
|
||||
|
||||
@log_activity()
|
||||
def erroring(agent, log_context=None):
|
||||
yield {"type": "error", "error": "No LLM class found for type foundry"}
|
||||
|
||||
with patch("application.logging._log_activity_to_db"), \
|
||||
caplog.at_level(_logging.INFO, logger="root"):
|
||||
list(erroring(FakeAgent()))
|
||||
|
||||
finished = next(r for r in caplog.records if r.message == "activity_finished")
|
||||
assert finished.status == "error"
|
||||
assert finished.error_class == "StreamError"
|
||||
assert finished.answer_length == 0
|
||||
|
||||
def test_log_activity_emits_response_summary_aggregates(self, caplog):
|
||||
# Replaces the ``agent_response`` event that ``run_agent_logic``
|
||||
# used to emit only on the Celery webhook path: every Flask
|
||||
|
||||
Reference in new issue
Block a user