Skip to content

instrument(conversation): enrich empty-response WARN with response-shape diagnostics (#67 partial)#68

Merged
PowerCreek merged 1 commit into
mainfrom
issue-67-empty-response-diagnostics
May 24, 2026
Merged

instrument(conversation): enrich empty-response WARN with response-shape diagnostics (#67 partial)#68
PowerCreek merged 1 commit into
mainfrom
issue-67-empty-response-diagnostics

Conversation

@PowerCreek

Copy link
Copy Markdown

Partial close of #67 — diagnostic visibility, not the root-cause fix.

Summary

  • Adds pure-function helper _diagnose_empty_response(...) near the top of agent/conversation_loop.py that extracts response-shape context from any provider's assistant_message object.
  • Enriches the existing WARN log line at the empty-response retry trigger (~line 3608) with: provider, finish_reason, tool_calls_count, response_len, prior_was_tool, response_id.
  • No semantic change — same retry logic, same fallback chain, same status messages. Operators now see WHY a response was flagged as empty in the log line itself, with no separate instrumentation pass needed.

Why

The user reports Empty response from model — retrying (1/3) from polynomial-explorer worker across THREE providers (claude-cli, openai, silo-mistral). That universality rules out provider-specific bugs and points at something common in the request/response pipeline. The existing WARN line only says (model=%s) — not enough to distinguish:

  • Genuinely empty content (model returned nothing)
  • content="" + tool_calls=[…] (tool-call response that the empty detector mis-flags)
  • finish_reason="length" (response truncated; bigger context needed)
  • prior_was_tool (model returning empty after tool result — different recovery path)
  • Provider-specific edge (e.g. response_id matches a known bad batch)

After this lands, the operator's next failing worker turn produces a log line carrying all of that, so we can root-cause without speculative patches.

Test plan

  • 9 new tests in tests/agent/test_empty_response_diagnostic.py cover the helper in isolation:
    • object-shape with tool_calls list (count returned)
    • object-shape without tool_calls (0)
    • dict-shape with tool_calls (count from dict)
    • dict-shape without tool_calls (0)
    • missing-id / missing-finish_reason → sentinel "?"
    • exploding property getters on response object → sentinel "?" (distinguished from absent → 0)
    • agent without provider attr → sentinel "?"
    • prior_was_tool propagation
    • realistic scenario: tool_calls=[…] + content="" (the Bug: devagentic-local provider returns empty content; raw API works #67 hypothesis)
  • Helper is pure / no I/O / no provider SDK dependency.
  • conversation_loop.py syntax verified post-patch.

Not in this PR

Root cause of the empty-content failure itself — still pending the operator's devbox-side checklist results (see comment on #67). This PR makes that root-causing data-driven instead of speculative.

Notes

  • Helper accepts kwargs only (named arguments) so the call site is self-documenting at the WARN site.
  • Fail-soft per attribute: any AttributeError / RuntimeError from a provider's property getter collapses to a "?" sentinel so the log line always formats cleanly. The distinction between "?" (couldn't determine) and 0 (definitely absent) is preserved — useful when diagnosing whether a weird provider object is involved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant