koala73/worldmonitor · error
Exa returned malformed structured summary for ${url}
Error message
Exa returned malformed structured summary for ${url} What it means
After getting a result row, extract() runs parseStructuredSummary on item.summary: an object passes through, a string is fence-stripped (```json) and JSON.parsed. null — and this throw — means the summary was absent, a non-string primitive, or a string whose JSON either failed to parse or parsed to a non-object: the model returned prose instead of schema-conformant JSON.
Source
Thrown at consumer-prices-core/src/acquisition/exa.ts:107
.filter(([, field]) => field.required !== false)
.map(([key]) => key),
additionalProperties: false,
};
// exa-js does not expose an AbortSignal for getContents, so use the API
// directly here to keep the opt-in fallback genuinely bounded.
// `text` rides along in the same call as ground truth for the caller's
// on-page price verification (price-evidence.ts) — same request, no
// second fetch.
const result = await this.request<{ results?: Array<{ summary?: unknown; text?: unknown }> }>('/contents', {
urls: [url],
summary: { query: prompt, schema: outputSchema },
text: { maxCharacters: 30_000 },
}, opts.timeout);
const item = result.results?.[0];
if (!item) throw new Error(`Exa returned no content for ${url}`);
const data = parseStructuredSummary<T>(item.summary);
if (data === null) throw new Error(`Exa returned malformed structured summary for ${url}`);
return {
url,
data,
provider: this.name,
fetchedAt: new Date(),
...(typeof item.text === 'string' && item.text.trim() ? { pageContent: item.text } : {}),
};
}
async validate(): Promise<boolean> {
try {
await this.client.search('test', { numResults: 1 });
return true;
} catch {
return false;
}
}View on GitHub (pinned to eeab0a219f)
Solutions
- Retry the call once — off-format model output is frequently transient
- Simplify and tighten schema field descriptions so the model stays in structured mode
- Confirm the summary.schema parameter is still supported for your Exa plan/API version
- Treat as a page-level failure: record the miss and advance to the next candidate URL rather than disabling the provider
Example fix
// before
const data = parseStructuredSummary<T>(item.summary);
if (data === null) throw new Error(`Exa returned malformed structured summary for ${url}`);
// after — one retry for transient off-format output, then record the miss
let data = parseStructuredSummary<T>(item.summary);
if (data === null) {
const retry = await this.request('/contents', body, opts.timeout);
data = parseStructuredSummary<T>(retry.results?.[0]?.summary);
}
if (data === null) return { url, data: null, provider: this.name, fetchedAt: new Date() }; Defensive patterns
Strategy: retry
Type guard
// Narrow a summary payload before trusting it (mirrors parseStructuredSummary)
function isStructuredSummary<T>(v: unknown): v is T {
if (v && typeof v === 'object') return true;
if (typeof v !== 'string') return false;
try {
const p = JSON.parse(v.trim().replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, ''));
return !!p && typeof p === 'object';
} catch { return false; }
} Try / catch
try {
return await exa.extract(url, schema);
} catch (err) {
if (err instanceof Error && /malformed structured summary/.test(err.message)) {
return await exa.extract(url, schema); // model output is nondeterministic — retry once
}
throw err;
} Prevention
- Retry off-format summaries once; log the raw summary on the second failure for schema tuning
- Keep schema field descriptions short and imperative to hold the model in JSON mode
- Re-verify extraction quality after any Exa model/API update
When it happens
Trigger: Exa's summary model answering with narrative text instead of JSON (nondeterministic); the summary.schema not being honored by the API tier in use so no structured summary comes back; schema/prompt drift after an Exa model or API update.
Common situations: Sporadic malformed outputs sprinkled across a large extraction batch; behavior change after an Exa API/model revision; overly long field descriptions pushing the model off-format.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- get-china-decision-signals returned no canonical payload
- --args must be valid JSON: ${err.message}
- Exa returned no content for ${url}
- Exa ${endpoint.slice(1)} failed HTTP ${response.status}: ${d
- EXA_API_KEY is required for exa-search adapter
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/e0971f53673d4ad3.
Report an issue: GitHub.