rohitg00/agentmemory · error · Error
OpenAI returned unexpected response: ${JSON.stringify(data).
Error message
OpenAI returned unexpected response: ${JSON.stringify(data).slice(0, 200)} What it means
After a 2xx response, OpenAIProvider.call expects choices[0].message.content (or, per issue #627, reasoning / reasoning_content fields emitted by DeepSeek V4, Qwen3, GLM, Kimi and older o-series). If none of these fields is present it throws with a 200-char JSON dump of the actual body. This catches protocol drift in OpenAI-compatible endpoints that return 200 with an unexpected shape.
Source
Thrown at src/providers/openai.ts:148
const data = (await response.json()) as {
choices?: Array<{
message?: { content?: string; reasoning?: string; reasoning_content?: string };
}>;
};
const message = data.choices?.[0]?.message;
const content = message?.content;
if (content) {
return content;
}
// Fallback: some thinking models return reasoning but no content.
// DeepSeek V4 / Qwen3 / GLM / Kimi return `reasoning_content`;
// older OpenAI o-series + some compatibles return `reasoning`. #627
const reasoning = message?.reasoning ?? message?.reasoning_content;
if (reasoning) {
return reasoning;
}
throw new Error(
`OpenAI returned unexpected response: ${JSON.stringify(data).slice(0, 200)}`,
);
}
}
// Resolves the outbound-fetch timeout for the OpenAI LLM path.
// Precedence (preserving v0.9.17 behaviour):
// 1. OPENAI_TIMEOUT_MS — OpenAI-scoped alias (back-compat)
// 2. AGENTMEMORY_LLM_TIMEOUT_MS — global LLM/embedding timeout (#446)
// 3. 60 000 ms default
function resolveTimeout(): number {
const openaiRaw = getEnvVar("OPENAI_TIMEOUT_MS");
const openai = parsePositiveInt(openaiRaw);
if (openai !== undefined) return openai;
const globalRaw = getEnvVar("AGENTMEMORY_LLM_TIMEOUT_MS");
const globalMs = parsePositiveInt(globalRaw);
if (globalMs !== undefined) return globalMs;View on GitHub (pinned to e04ba88819)
Solutions
- Read the 200-char JSON dump in the message to see the actual response shape
- If content is filtered/empty, adjust the prompt or model to avoid the filter
- If using a compatible gateway, ensure it proxies real Chat Completions responses (choices[0].message.content)
- Check the endpoint is a chat/completions URL, not embeddings or /responses
- File/patch support for the new field name alongside reasoning/reasoning_content in openai.ts
Example fix
// before baseUrl: 'https://api.example.com/v1/embeddings' // 200 but no choices -> unexpected response // after baseUrl: 'https://api.example.com/v1/chat/completions'
Defensive patterns
Strategy: type-guard
Validate before calling
assert(config.baseUrl?.includes('/chat/completions') ?? true, 'OpenAI provider baseUrl must point at a chat/completions endpoint'); Type guard
interface ChatCompletionResponse { choices?: Array<{ message?: { content?: string; reasoning?: string; reasoning_content?: string } }> }
function hasText(r: ChatCompletionResponse): boolean {
const msg = r.choices?.[0]?.message;
return !!msg && !!(msg.content ?? msg.reasoning ?? msg.reasoning_content);
} Try / catch
try {
return await provider.call(prompt);
} catch (e) {
if ((e as Error).message.startsWith('OpenAI returned unexpected response')) {
console.error('Endpoint returned a non-chat-completions 200 body; check baseUrl/model');
return await fallbackProvider.call(prompt);
}
throw e;
} Prevention
- Point the OpenAI-compatible baseUrl at /chat/completions, never /embeddings or /responses
- Smoke-test third-party compatible gateways before adopting them
- Keep openai.ts's field aliases (content/reasoning/reasoning_content) in sync with upstream protocol changes
- Adjust prompts that trigger content filtering, which yields 200 with empty content
When it happens
Trigger: compress()/summarize() against an OpenAI-compatible endpoint whose 200 response lacks choices[0].message.content, reasoning, and reasoning_content — e.g. an empty choices array because content was filtered, a chat-completions-shaped call against an embeddings/responses endpoint, or a compat server returning a novel schema.
Common situations: Pointing the OpenAI provider at a third-party compatible gateway that changed its response schema; calling the new OpenAI Responses API path instead of Chat Completions; safety filters returning empty content with finish_reason 'content_filter'; reasoning-only responses whose content field is null on an endpoint not covered by the known reasoning aliases.
Related errors
- ${this.name} returned unexpected response: ${JSON.stringify(
- API key is required (via constructor, OPENAI_EMBEDDING_API_K
- OpenAI embedding failed (${response.status}): ${err}
- OPENAI_API_KEY is required for the openai provider
- OpenAI API request timed out after ${this.timeoutMs}ms — set
AI-assisted analysis of rohitg00/agentmemory@e04ba88819 (2026-08-30).
Data as JSON: /api/errors/7613ab70d50f2428.
Report an issue: GitHub.