Repository navigation
bug: diagnose whitespace-only LLM responses from third-party relays #815
Description
Summary
GenericAgent can receive a syntactically valid but semantically empty completion: for example, the response payload contains one text block whose value is a single whitespace character (" "). The TUI then shows:
· LLM returned an empty response. Retrying...
The retry succeeds in the observed case, so this is recoverable, but it is confusing to diagnose.
Observation
- Session trace:
model_responses_858881.txt, 2026-09-21 16:27:46. - The response was represented as a Python object equivalent to:
[{"type": "text", "text": " "}] - There was no meaningful
contentorthinking; GA correctly treated it as blank and regenerated. - The relevant guard is
ga.py/do_no_tool()(currently around lines 459–467). It emits[Warn] LLM returned an empty response. Retrying...and routes through_retry_or_exit(). - The TUI replaces
[Warn]with·, which makes the displayed line look less like an actionable warning.
Important context: third-party relays / gateways
This has been observed when using a non-official API relay/gateway rather than the model provider's official endpoint. The relay may return an HTTP-successful response while dropping, truncating, or normalizing streamed content to whitespace. This is an observation, not a claim that every relay is faulty; the same symptom could also originate upstream. However, intermediaries make the failure mode substantially harder to attribute because the request technically succeeds.
There is a related lower-level path in llmcore.py where a stream produces no output and is retried as ConnectionError("empty response"), but the whitespace-only case reaches the agent-level blank-response guard instead.
Why this deserves a separate issue
#201 added the useful retry limit (retry up to three times, then stop). It does not expose enough context to distinguish:
- a provider-side empty completion,
- a relay/gateway that returned a malformed or whitespace-only successful response, and
- a stream that ended before producing any content.
Suggested improvements
- Keep the current safe retry behavior.
- Log a compact, redacted diagnostic for blank responses: session/model name, configured base URL host (no credentials), response block types, text lengths / whitespace-only flag, whether any thinking was present, and retry count.
- Make the TUI warning retain a visible severity label, e.g.
· [Warn] Empty LLM response; retrying (1/3). - Optionally classify
whitespace_only,no_stream_output, andempty_payloadseparately in the trace/logs. - Documentation: recommend reproducing against the official endpoint before filing provider-specific issues when a third-party relay is in the path.
This should make relay-related incidents diagnosable without weakening compatibility with OpenAI-compatible gateways.
Activity
louisss1016 commentedon Oct 1, 2026
I'd like to take this one. Plan, following the issue's suggested improvements:
ga.pydo_no_tool(), keep the safe retry path unchanged, and emit a compact redacted diagnostic on blank responses: classification (whitespace_onlyvsempty_payload), response block types, content/thinking length and whitespace flag, retry count, model name, and the gateway host only (no path, no credentials).frontends/tui_v3.py, stop stripping[Warn]/[Warning]/[Error]into a bare·; render them as· [Warn] ...so the severity survives.frontends/tests/for the classifier and the TUI prefix rule.The retry behavior itself (up to 3, then stop) stays exactly as it is — this is diagnosability only, no compatibility change for OpenAI-compatible gateways.
louisss1016 commentedon Oct 1, 2026
PR up: #819
Summary of the implementation:
ga.pygainsdescribe_blank_response()(kind / sizes / block types / model / gateway host — hostname only, never credentials) and_url_host();do_no_tool()'s retry semantics are untouched (3 attempts then exit), only the warning line gains the attempt counter + diagnostic.tui_v3.pysplits_ACTION_REso[Warn]/[Warning]/[Error]now render as· [Warn] …instead of a neutral bullet.Left out on purpose: the
llmcore.pyno_stream_outputpath (already a distinctConnectionError), and suggestion #5's docs note — that one feels like a maintainer call, happy to add it if you want it in this PR.