BigPizzaV3/CodexPlusPlus · error · Error
API 未返回有效的 choices[0].message.content,请确认兼容 Chat Completions
Error message
API 未返回有效的 choices[0].message.content,请确认兼容 Chat Completions
What it means
completionText() validates that the response contains a non-empty choices[0].message.content (string or array of text parts). If content is missing, empty, or whitespace-only, the response is not a usable Chat Completions completion and this error is thrown.
Solutions
- Verify baseUrl points to a Chat Completions-compatible endpoint (…/v1/chat/completions)
- Check the API returns standard OpenAI-style choices[0].message.content
- Use a model that emits text content (not reasoning-only output)
- Log the raw response body to inspect the actual shape
Example fix
// before baseUrl: 'https://api.openai.com/v1/responses' // after baseUrl: 'https://api.openai.com/v1' // + /chat/completions
Defensive patterns
Strategy: type-guard
Validate before calling
const ok = b => Array.isArray(b?.choices) && b.choices.length>0 && b.choices[0]?.message && (typeof b.choices[0].message.content==='string' ? b.choices[0].message.content.trim() : Array.isArray(b.choices[0].message.content));
Type guard
function hasTextContent(body){const c=body?.choices?.[0]?.message?.content;if(typeof c==='string')return c.trim().length>0;if(Array.isArray(c))return c.some(p=>p?.type==='text'&&String(p.text||'').trim());return false;} Try / catch
try{ await organizeLong(...) }catch(e){ if(e.message.includes('choices[0]')){ logRawResponse(); verifyEndpointCompatibility(); } } Prevention
- Confirm the endpoint is OpenAI Chat Completions compatible
- Test the endpoint with a minimal curl request first
- Avoid reasoning-only models that return no text content
When it happens
Trigger: Response body has no choices, empty choices array, message.content null/empty/whitespace, or content parts array contains no type==='text' entries.
Common situations: Endpoint is not actually Chat Completions compatible (e.g. Responses API or wrong path); provider returned an error-shaped body that still parsed as JSON; reasoning-only model returning no text content; wrong baseUrl hitting an HTML page that slipped past JSON check.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/0d9f3325218a1014.
Report an issue: GitHub.
Appendix: source
Thrown at tools/conversation-canvas/public/canvas.user.js:701
else buffer+=text;
if(sse){let end;while((end=buffer.indexOf('\n'))>=0){consume(buffer.slice(0,end).replace(/\r$/,''));buffer=buffer.slice(end+1);}}
progress({bytes:size,chars:content.length});if(done)break;
}
const tail=decoder.decode();if(sse){buffer+=tail;if(buffer.trim())consume(buffer.replace(/\r$/,''));
if(!done&&!finish)throw Object.assign(Error('API 流式连接提前结束,本批未提交'),{canvasApiLocal:true,retryable:true});
return {choices:[{finish_reason:finish,message:{content}}]};
}
try{return JSON.parse(raw+tail);}catch{throw Object.assign(Error('API 返回的不是 JSON,请检查 API 地址'),{canvasApiLocal:true});}
}finally{void reader.cancel().catch(()=>{});try{reader.releaseLock();}catch{}}
}
function completionText(body){
const choice=body?.choices?.[0];
if(choice?.finish_reason==='length')throw Error('API 输出达到长度上限,本批未提交;请使用输出容量更大的模型');
if(choice?.finish_reason==='content_filter'||choice?.message?.refusal)throw Error('API 未能生成本批整理结果');
const content=choice?.message?.content;
const value=typeof content==='string'?content:Array.isArray(content)?content.filter(p=>p?.type==='text').map(p=>p.text||'').join(''):'';
if(!value.trim())throw Error('API 未返回有效的 choices[0].message.content,请确认兼容 Chat Completions');
return value;
}
function waitForApi(promise,signal){
signal?.throwIfAborted();
if(!signal)return promise;
return new Promise((resolve,reject)=>{
const abort=()=>{signal.removeEventListener('abort',abort);reject(signal.reason||new DOMException('Aborted','AbortError'));};
signal.addEventListener('abort',abort,{once:true});
promise.then(value=>{signal.removeEventListener('abort',abort);resolve(value);},error=>{signal.removeEventListener('abort',abort);reject(error);});
});
}
function createApiOrganizer({request,timeoutMs=300000,maxRetries=2,retryDelayMs=2000}){
const requests=new Map();
const closed=()=>new DOMException('整理连接已关闭','AbortError');
const dispose=()=>{for(const entry of requests.values())entry.controller.abort(closed());requests.clear();};
async function run(config,prompt,requestId,session,onSession,signal,progress=()=>{}){View on GitHub (pinned to b1ed92e5e4)