{"record":{"id":"9d91939cfc8b50db","repo":"sipeed/picoclaw","slug":"no-choices-in-response","errorCode":null,"errorMessage":"no choices in response","messagePattern":"no choices in response","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/membench/llm_client.go","lineNumber":183,"sourceCode":"\t\t\tcontinue // rate limit or server error → retry\n\t\t}\n\t\tif resp.StatusCode != 200 {\n\t\t\treturn \"\", fmt.Errorf(\"API error %d: %s\", resp.StatusCode, string(respBody))\n\t\t}\n\n\t\tlastErr = nil\n\t\tbreak\n\t}\n\tif lastErr != nil {\n\t\treturn \"\", fmt.Errorf(\"after %d retries: %w\", c.MaxRetries, lastErr)\n\t}\n\n\tvar chatResp chatResponse\n\tif err := json.Unmarshal(respBody, &chatResp); err != nil {\n\t\treturn \"\", fmt.Errorf(\"parse response: %w\", err)\n\t}\n\tif len(chatResp.Choices) == 0 {\n\t\treturn \"\", fmt.Errorf(\"no choices in response\")\n\t}\n\tcontent := strings.TrimSpace(chatResp.Choices[0].Message.Content)\n\t// Strip any residual <think>...</think> blocks\n\tif idx := strings.Index(content, \"</think>\"); idx >= 0 {\n\t\tcontent = strings.TrimSpace(content[idx+len(\"</think>\"):])\n\t}\n\t// Fallback: GLM/DeepSeek put thinking output in reasoning_content when thinking is enabled\n\tif content == \"\" && chatResp.Choices[0].Message.ReasoningContent != \"\" {\n\t\tcontent = strings.TrimSpace(chatResp.Choices[0].Message.ReasoningContent)\n\t}\n\tif content == \"\" {\n\t\treturn \"\", fmt.Errorf(\"empty LLM response\")\n\t}\n\treturn content, nil\n}\n","sourceCodeStart":165,"sourceCodeEnd":199,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/cmd/membench/llm_client.go#L165-L199","documentation":"The provider returned HTTP 200 with a syntactically valid JSON body whose choices array is empty (llm_client.go:183). Unlike transport or parse failures, the API worked but produced zero completions — typically content filtering, an empty generation, or a provider quirk. The client treats it as terminal rather than retrying.","triggerScenarios":"Content-policy filter dropping the only candidate; provider returning {\"choices\":[]} on overloaded internal routing; empty completion after max_tokens=0 or a misconfigured sampling payload; some proxies stripping choices on transform.","commonSituations":"Benchmark prompts with adversarial or sensitive LOCOMO content tripping filters; reasoning models returning all output in a field the client ignores; near-zero max_tokens; flaky third-party gateways.","solutions":["Log the full response body — finish_reason/usage usually reveals filtering or truncation","Retry the single request: empty choices is often transient on self-hosted backends","Raise max_tokens and check the model ID supports your content type","If filtering is persistent, switch model or sanitize the prompt"],"exampleFix":"// before\nif len(chatResp.Choices) == 0 {\n    return \"\", fmt.Errorf(\"no choices in response\")\n}\n\n// after\nif len(chatResp.Choices) == 0 {\n    return \"\", fmt.Errorf(\"no choices in response (id=%s, usage=%+v, body=%s)\", chatResp.ID, chatResp.Usage, truncate(respBody, 512))\n}","handlingStrategy":"retry","validationCode":"// cheap preflight for content-filter-prone models: send a 1-token probe\nif out, err := llm.Complete(ctx, \"ping\"); err != nil || out == \"\" {\n    return fmt.Errorf(\"model %s not producing completions (probe failed: %v)\", model, err)\n}","typeGuard":null,"tryCatchPattern":"if len(chatResp.Choices) == 0 {\n    if retried < 2 && resp.StatusCode == 200 {\n        log.Printf(\"empty choices (usage=%+v); retrying once\", chatResp.Usage)\n        time.Sleep(time.Second)\n        goto retry // or restructure as a loop\n    }\n    return \"\", fmt.Errorf(\"no choices in response: body=%s\", truncate(respBody, 256))\n}","preventionTips":["Log the full body when choices is empty — finish_reason reveals filtering vs. truncation","Set max_tokens > 0 and verify the model ID exists before the run","For reasoning models, check whether output landed in a field the client ignores","Retry empty-choices once or twice; on self-hosted backends it is frequently transient"],"tags":["go","llm-client","response","content-filter"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}