Skip to content

fix: lengthen requestRetry hold so grok-4.6 thinking is not withheld - #989

Merged
chenyme merged 10 commits into
chenyme:mainfrom
lij768423-svg:feat/request-retry-hold-defaults
Aug 20, 2026
Merged

fix: lengthen requestRetry hold so grok-4.6 thinking is not withheld#989
chenyme merged 10 commits into
chenyme:mainfrom
lij768423-svg:feat/request-retry-hold-defaults

Conversation

@lij768423-svg

Copy link
Copy Markdown
Contributor

Why

qualityGuard.requestRetry currently defaults to holdTimeout: 3s and minOutputTokens: 32. grok-4.6 (and grok-4.5 high-effort) often emit encrypted_content at the end of the stream. A 3s peek sees ≥32 visible tokens with no ciphertext, classifies the account as missing-thinking, and writes:

  • audit 200 · 错误 / quality_degraded
  • then 503 after retries / 499 when the client gives up

That is a false withhold, not a client-visible “fake thinking” leak. Production lab (v3.1.4-18183-hold-tools) already uses a 30s hold (the allowed maximum) and does not see this audit storm on the same code.

What changed

When requestRetry fields are omitted (and in config.example.yaml):

field before after
holdTimeout 3s 30s (validator max)
minOutputTokens 32 8 (validator min)
accountCooldown 24h 12h
idleAccountCooldown 24h 15m
probe model grok-4.5 grok-4.6
  • Example YAML now recommends requestRetry.enabled: true.
  • Built-in Go Enabled stays false, so existing configs without requestRetry do not flip on upgrade.
  • Sidecar qualityGuard.enabled stays false; mode: hybrid is unchanged. This PR is request-path hold defaults only.

Upgrade

Already-running installs that copied the old 3s example keep 3s until they edit config.yaml and restart:

docker compose --profile quality-guard restart grok2api

New copies of config.example.yaml get the 30s hold. Clients should allow ≥120s; 6 × 30s hold plus retries will otherwise 499.

lij768423-svg and others added 10 commits August 20, 2026 09:11
Usage.reasoning_tokens, empty reasoning items, and the Chat SSE stub
are not proof of thinking. Degraded upstreams fill those fields and
then dump visible tokens, so the hold used to fail-open as 200.

Require reasoning/summary deltas or encrypted_content before delivering.
An empty stub waits until terminal so ciphertext that arrives at the end
is not withheld.
Grok CLI treats annotations as a required field. A missing key fails
deserialization with "missing field annotations" and the TUI retries
the turn. Keep existing citation arrays untouched.
Grok TUI declares a tools array on every agent request, including the
first thinking turn. Skipping hold on that schema forwarded
missing-thinking streams as HTTP 200.

Keep the replay-safety skip for in-flight tool output
(function_call_output, tool_result, role=tool). Compact skip and
previous_response_id pinning are unchanged.
response.completed / [DONE] with zero tokens used to keep the quality
hold in Wait until the idle timeout. The client already had HTTP 200
headers and saw a 50–120s Retrying spinner.

Finish the peek immediately and surface errQualityEmptyStream so the
attempt loop rotates. Open empty hangs still wait for idle and do not
fail-open as HTTP 200.
Empty SSE idle timeouts currently write a hardcoded 24h cooldown via
qualityIdleAccountCooldown. Toggling enabled does not reset health, so a
single-account pool stays on 429 upstream_cooling until the operator
edits sqlite and restarts.

Add POST /api/admin/v1/accounts/:id/clear-cooldown (API + UI) which
zeros failure_count/cooldown_until/last_error. UpdateHealth already
publishes InvalidationAccountHealthChanged, so runtimeStore=memory
selector overlays are replaced.

Expose qualityGuard.requestRetry.idleAccountCooldown (1m–168h, default
24h) independently of missing-thinking accountCooldown. PATCH enabled
while cooling returns enabledDoesNotClearCooldown so the UI can warn.

Closes chenyme#976
Grok TUI tools run locally. The next /v1/responses body already contains
function_call_output; retrying it on another account only regenerates the
model turn and does not re-execute tools. Skipping hold on that body let
0-thinking dumps through on the common agent loop.
TUI 0.2.93 treats any response.incomplete as fatal max_tokens_truncation,
ignoring incomplete_details.reason, so stream aborts killed the turn.
serde also requires model on response.failed / completed; a missing key
fails deserialization. Always emit response.failed and fill model from
stream metadata or the last seen response event.
3s hold cuts encrypted thinking that arrives at end-of-stream, which
fills audits with HTTP 200 · error and quality_degraded retries.
Match the production lab defaults: 30s hold, minOutput 8, 12h
missing-thinking cooldown, 15m idle cooldown, grok-4.6 probe model.

requestRetry.enabled stays false in built-in Go defaults so existing
configs do not flip on upgrade; config.example.yaml now recommends it on.
# Conflicts:
#	backend/internal/application/gateway/quality_retry.go
#	backend/internal/application/gateway/quality_retry_scan.go
#	backend/internal/application/gateway/quality_retry_test.go
#	backend/internal/infra/config/config.go
#	backend/internal/infra/config/config_test.go
#	backend/internal/transport/http/account/handler.go
#	backend/internal/transport/http/account/handler_test.go
#	backend/internal/transport/http/inference/handler.go
#	backend/internal/transport/http/inference/handler_test.go
#	config.example.yaml
@chenyme
chenyme merged commit 26b9b1e into chenyme:main Aug 20, 2026
9 checks passed
mcheiyue added a commit to mcheiyue/grok2api that referenced this pull request Aug 20, 2026
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.

2 participants