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

  1. Retry the call once — off-format model output is frequently transient
  2. Simplify and tighten schema field descriptions so the model stays in structured mode
  3. Confirm the summary.schema parameter is still supported for your Exa plan/API version
  4. 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

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

Related errors


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/e0971f53673d4ad3. Report an issue: GitHub.