{"record":{"id":"7613ab70d50f2428","repo":"rohitg00/agentmemory","slug":"openai-returned-unexpected-response-json-string","errorCode":null,"errorMessage":"OpenAI returned unexpected response: ${JSON.stringify(data).slice(0, 200)}","messagePattern":"OpenAI returned unexpected response: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/providers/openai.ts","lineNumber":148,"sourceCode":"\n    const data = (await response.json()) as {\n      choices?: Array<{\n        message?: { content?: string; reasoning?: string; reasoning_content?: string };\n      }>;\n    };\n    const message = data.choices?.[0]?.message;\n    const content = message?.content;\n    if (content) {\n      return content;\n    }\n    // Fallback: some thinking models return reasoning but no content.\n    // DeepSeek V4 / Qwen3 / GLM / Kimi return `reasoning_content`;\n    // older OpenAI o-series + some compatibles return `reasoning`. #627\n    const reasoning = message?.reasoning ?? message?.reasoning_content;\n    if (reasoning) {\n      return reasoning;\n    }\n    throw new Error(\n      `OpenAI returned unexpected response: ${JSON.stringify(data).slice(0, 200)}`,\n    );\n  }\n}\n\n// Resolves the outbound-fetch timeout for the OpenAI LLM path.\n// Precedence (preserving v0.9.17 behaviour):\n//   1. OPENAI_TIMEOUT_MS       — OpenAI-scoped alias (back-compat)\n//   2. AGENTMEMORY_LLM_TIMEOUT_MS — global LLM/embedding timeout (#446)\n//   3. 60 000 ms default\nfunction resolveTimeout(): number {\n  const openaiRaw = getEnvVar(\"OPENAI_TIMEOUT_MS\");\n  const openai = parsePositiveInt(openaiRaw);\n  if (openai !== undefined) return openai;\n\n  const globalRaw = getEnvVar(\"AGENTMEMORY_LLM_TIMEOUT_MS\");\n  const globalMs = parsePositiveInt(globalRaw);\n  if (globalMs !== undefined) return globalMs;","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/rohitg00/agentmemory/blob/e04ba88819c365c9acf9d6661ea802143e728bd6/src/providers/openai.ts#L130-L166","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nbaseUrl: 'https://api.example.com/v1/embeddings' // 200 but no choices -> unexpected response\n\n// after\nbaseUrl: 'https://api.example.com/v1/chat/completions'","handlingStrategy":"type-guard","validationCode":"assert(config.baseUrl?.includes('/chat/completions') ?? true, 'OpenAI provider baseUrl must point at a chat/completions endpoint');","typeGuard":"interface ChatCompletionResponse { choices?: Array<{ message?: { content?: string; reasoning?: string; reasoning_content?: string } }> }\nfunction hasText(r: ChatCompletionResponse): boolean {\n  const msg = r.choices?.[0]?.message;\n  return !!msg && !!(msg.content ?? msg.reasoning ?? msg.reasoning_content);\n}","tryCatchPattern":"try {\n  return await provider.call(prompt);\n} catch (e) {\n  if ((e as Error).message.startsWith('OpenAI returned unexpected response')) {\n    console.error('Endpoint returned a non-chat-completions 200 body; check baseUrl/model');\n    return await fallbackProvider.call(prompt);\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["openai","response-parsing","schema-mismatch","compatibility"],"backgroundTag":"unexpected-response-schema","analyzedSha":"e04ba88819c365c9acf9d6661ea802143e728bd6","analyzedAt":"2026-08-30T01:07:40.754Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}