mastra-ai/mastra · error

Only `TextNode` is allowed for `Summary` extractor

Error message

Only `TextNode` is allowed for `Summary` extractor

What it means

SummaryExtractor.extract() requires every node in the input array to be an instance of TextNode. Summary generation works on text content, so other node types (or plain objects mimicking nodes) are rejected before any LLM call.

Source

Thrown at packages/rag/src/document/extractors/summary.ts:103

      const result = await miniAgent.generateLegacy([{ role: 'user', content: prompt }]);
      summary = result.text;
    }

    if (!summary) {
      console.warn('Summary extraction LLM output returned empty');
      return '';
    }

    return summary.replace(STRIP_REGEX, '');
  }

  /**
   * Extract summaries from a list of nodes.
   * @param {BaseNode[]} nodes Nodes to extract summaries from.
   * @returns {Promise<ExtractSummary[]>} Summaries extracted from the nodes.
   */
  async extract(nodes: BaseNode[]): Promise<ExtractSummary[]> {
    if (!nodes.every(n => n instanceof TextNode)) throw new Error('Only `TextNode` is allowed for `Summary` extractor');

    const nodeSummaries = await Promise.all(nodes.map(node => this.generateNodeSummary(node)));

    const metadataList: ExtractSummary[] = nodes.map(() => ({}));

    for (let i = 0; i < nodes.length; i++) {
      if (i > 0 && this.prevSummary && nodeSummaries[i - 1]) {
        metadataList[i]!['prevSectionSummary'] = nodeSummaries[i - 1];
      }
      if (i < nodes.length - 1 && this.nextSummary && nodeSummaries[i + 1]) {
        metadataList[i]!['nextSectionSummary'] = nodeSummaries[i + 1];
      }
      if (this.selfSummary && nodeSummaries[i]) {
        metadataList[i]!['sectionSummary'] = nodeSummaries[i];
      }
    }

    return metadataList;

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Filter the input: nodes.filter(n => n instanceof TextNode) before calling extract.
  2. If nodes came from JSON, revive them with the proper class (e.g. new TextNode(...)/TextNode.fromJSON) so instanceof passes.
  3. Split the pipeline so only text-bearing nodes go to the SummaryExtractor.

Example fix

// before
await extractor.extract(allNodes);
// after
await extractor.extract(allNodes.filter(n => n instanceof TextNode));
Defensive patterns

Strategy: type-guard

Validate before calling

const textNodes = nodes.filter((n): n is TextNode => n instanceof TextNode);
if (textNodes.length === 0) return [];

Type guard

const isTextNode = (n: BaseNode): n is TextNode => n instanceof TextNode;

Try / catch

try {
  return await extractor.extract(nodes);
} catch (e) {
  if (e instanceof Error && e.message.includes('Only `TextNode` is allowed')) {
    return extractor.extract(nodes.filter(isTextNode));
  }
  throw e;
}

Prevention

When it happens

Trigger: extract(nodes) where nodes mixes TextNode with other BaseNode subclasses (e.g. ImageDocument/ImageNode) or contains objects deserialized as plain JSON without the TextNode prototype.

Common situations: Running extractors over a heterogeneous node list produced by a splitter that emits multiple node types, or passing nodes restored from JSON storage that lost their class identity (no Object.setPrototypeOf / not revived via TextNode.fromJSON).

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/2590fbac6bc61c5b. Report an issue: GitHub.