Skip to content

fix(goal/recovery): boundedllm falls back to reasoning when content is empty / content 为空且 reasoning 有内容时以推理文本作为响应,避免 thinking 模型上 goal evaluator 空响应暂停 (#9678) - #9785

Open
myipanta wants to merge 1 commit into
esengine:main-v2from
myipanta:fix/9678-boundedllm-reasoning-only

Conversation

@myipanta

@myipanta myipanta commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #9678. When the Goal evaluator (or recovery reviewer) runs on a thinking model as the fallback (recovery_model → guardian_model → main model), the model streams its verdict into reasoning_content and finishes with an empty visible content block. boundedllm.Call only collected ChunkText and dropped ChunkReasoning, so it returned an empty response and the goal fail-closed paused on every turn ("empty goal evaluator response"), even though output tokens were consumed.

internal/boundedllm/bounded.go:

  • Call now also accumulates ChunkReasoning into its own builder, bounded by maxOutputBytes just like text.
  • When the stream ends with empty visible content but non-empty reasoning and no error, the reasoning text is returned as the response. Callers (goaleval, completioneval, recovery reviewer) extract a JSON verdict between the first and last {/}, so a verdict emitted at the end of the thinking stream parses directly.
  • Visible-text requests are unchanged (reasoning is never mixed into a non-empty text response). Empty streams still return "" (fail-closed upstream). Stream errors still surface instead of leaking the reasoning-before-error.

This complements the main-loop fix #6617 (reasoningOnlyFinishHonoured) on the bounded reviewer channel.

Issues

Fixes #9678

Verification

  • New tests in internal/boundedllm/bounded_test.go:
    • TestCallReturnsVisibleTextWhenPresent — reasoning never leaks into a present visible answer.
    • TestCallFallsBackToReasoningWhenContentEmpty — reasoning-only stream returns the reasoning text.
    • TestCallReturnsEmptyWhenNothingStreamed — truly empty stays empty (fail-closed).
    • TestCallSurfacesStreamErrorWithoutReasoningFallback — an error after reasoning must surface, not return the thinking.
  • go test ./internal/boundedllm/ ./internal/goaleval/ ./internal/completioneval/ ./internal/recovery/
  • go test ./internal/control/ go test ./internal/agent/
  • go vet on all affected packages; gofmt clean.

Documentation impact

Documentation-impact: none - internal reviewer-channel fallback only; no user-facing config or docs/*.md change.

Cache impact

Cache-impact: none - bounded reviewer calls are isolated run-time requests (temperature 0, no tools/history), not part of the cache-stable system-prompt prefix or provider-visible tool schema. scripts/check-cache-impact.sh reports no cache-sensitive files changed.

Cache-guard: go test ./internal/boundedllm/ (new reasoning-fallback suite) plus the goal-evaluator/recovery/completion suites that run the same channel.

…s empty / content 为空且 reasoning 有内容时以推理文本作为响应,避免 thinking 模型上 goal evaluator 空响应暂停 (esengine#9678)
@github-actions github-actions Bot added the v2 Go rewrite (1.x) — main-v2 branch, active development label Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Go rewrite (1.x) — main-v2 branch, active development

Projects

None yet

2 participants