n8n-io/n8n · error · Error
Message content must be a string or contain text content
Error message
Message content must be a string or contain text content
What it means
`extractMessageContent` accepts content as either a plain string or an array of typed parts (e.g. `[{type:'text', text:'...'}]`). If content is some other shape (a number, an object, an array with no `type:'text'` items), the function cannot extract text and throws. The text-extraction path also strips context tags via `cleanContextTags`.
Source
Thrown at packages/@n8n/ai-workflow-builder.ee/evaluations/langsmith/types.ts:83
if (typeof content === 'string') {
return cleanContextTags(content);
}
if (Array.isArray(content)) {
// Extract text from complex content
const textContent = content
.filter((item) => item?.type === 'text')
.map((item) => (item as { text: string }).text)
.map(cleanContextTags)
.join('\n');
if (textContent) {
return textContent;
}
}
throw new Error('Message content must be a string or contain text content');
}
View on GitHub (pinned to 5ac6606e81)
Solutions
- Ensure each message has at least one `{type:'text', text:'...'}` part (or a top-level string `content`).
- If your messages legitimately have no text, skip them at the dataset/call site rather than passing them to `extractMessageContent`.
- If the SDK changed the content shape, update `extractMessageContent` to read the new field (the function already tolerates `message.kwargs?.content`).
Example fix
// before
// message.content = [{ type: 'image_url', image_url: { url: '...' } }] (no text part)
const text = extractMessageContent(message);
// after
// message.content = [{ type: 'text', text: 'describe this' }, { type: 'image_url', image_url: { url: '...' } }]
const text = extractMessageContent(message); Defensive patterns
Strategy: type-guard
Validate before calling
function hasTextContent(message: BaseMessage): boolean {
const c = (message as { content?: unknown }).content;
if (typeof c === 'string') return c.length > 0;
if (Array.isArray(c)) return c.some((p) => p?.type === 'text' && typeof p.text === 'string');
return false;
}
if (!hasTextContent(message)) throw new Error('message has no text part to extract');
const text = extractMessageContent(message); Type guard
function isTextualMessage(message: unknown): message is BaseMessage {
if (!message || typeof message !== 'object') return false;
const c = (message as { content?: unknown }).content;
if (typeof c === 'string') return true;
if (Array.isArray(c)) return c.some((p) => p?.type === 'text');
return false;
} Prevention
- Filter dataset examples to those with at least one text part before running.
- Pin the LangSmith SDK version so the message `content` shape does not change under you.
- If you support multimodal examples, branch on content type before calling the text-only extractor.
When it happens
Trigger: A message whose `content` is a non-string scalar, an object literal, or an array containing only non-text parts (e.g. only `{type:'image_url'}`). Also: a LangSmith SDK version that changed the content shape, or a custom message class whose `content` is structured differently.
Common situations: Multimodal examples that include only image/tool parts and no text; legacy message format where text lives under a different key; SDK upgrade that wraps content in an extra layer; malformed dataset upload.
Related errors
- No message provided
- No prompt found in inputs - expected "prompt" string or "mes
- LangSmith mode requires dataset to be a dataset name string
- LangSmith mode requires `--dataset` and does not support `--
- Cannot decrease maxIterations when resuming a run. Expected
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/b46276481ab8b7cc.
Report an issue: GitHub.