Budibase/budibase · error · Error
AI message content must be a string
Error message
AI message content must be a string
What it means
toPrompt converts Budibase AI chat messages into provider model messages, building a system instruction list and a modelMessages array. It enforces that every message's content is a plain string and rejects tool-role messages because the prompt pipeline only supports text conversation, not tool-call transcripts. Passing structured (array/parts) content or tool results therefore throws this Error.
Source
Thrown at packages/server/src/sdk/workspace/ai/llm/messages.ts:12
import type { Message } from "@budibase/types"
import type { ModelMessage } from "ai"
export function toPrompt(messages: Message[]): {
instructions?: string
messages: ModelMessage[]
} {
const instructions: string[] = []
const modelMessages: ModelMessage[] = []
for (const message of messages) {
if (typeof message.content !== "string") {
throw new Error("AI message content must be a string")
}
if (message.role === "tool") {
throw new Error("AI tool messages are not supported")
}
if (message.role === "system") {
instructions.push(message.content)
} else {
modelMessages.push({
role: message.role,
content: message.content,
} as ModelMessage)
}
}
return {
instructions: instructions.join("\n\n") || undefined,
messages: modelMessages,
}
}View on GitHub (pinned to a81a902e9a)
Solutions
- Flatten any non-string content into a plain string before calling toPrompt (e.g. join text parts).
- Filter or convert role "tool" messages out of the array before calling toPrompt.
- Only pass messages you constructed with string content; keep tool transcripts out of the prompt path.
- If tool support is needed, use the lower-level LLM API instead of toPrompt.
Example fix
// before
toPrompt(history.messages as ModelMessage[])
// after
const msgs = history.messages
.filter(m => m.role !== "tool")
.map(m => ({ ...m, content: typeof m.content === "string" ? m.content : JSON.stringify(m.content) }))
toPrompt(msgs) Defensive patterns
Strategy: validation
Validate before calling
const isPromptSafe = (msgs) => msgs.every(m => typeof m.content === "string" && m.role !== "tool")
if (!isPromptSafe(messages)) throw new Error("Messages must have string content and no tool role before toPrompt") Type guard
const isTextMessage = (m: ModelMessage): m is ModelMessage & { content: string } =>
typeof m.content === "string" Try / catch
try {
const prompt = toPrompt(messages)
} catch (e) {
if (e.message.includes("content must be a string") || e.message.includes("tool messages")) {
messages = messages.filter(m => m.role !== "tool").map(m => ({ ...m, content: typeof m.content === "string" ? m.content : JSON.stringify(m.content) }))
return toPrompt(messages)
}
throw e
} Prevention
- Always normalize provider responses (array content parts) to string content before building prompts
- Strip tool-role messages from any history you replay into toPrompt
- Type your history as messages with string content so TypeScript flags incompatible shapes
- Add a unit test that runs toPrompt over your persisted history format
When it happens
Trigger: Calling toPrompt with a messages array where any element has non-string content (e.g. AI SDK ModelMessage with array-of-parts content), or where any element has role "tool" (tool result messages from a prior tool-calling conversation).
Common situations: Feeding messages captured from a Vercel AI SDK / provider response back into toPrompt — those often carry array content blocks or tool results; persisting and replaying chat history that includes tool-call turns; a schema change in the message type from string content to parts.
Related errors
- AI message content must be a string
- Tool name must be under 64 characters long
- Cannot create a query tool without a query ID
- Unsupported BBAI model: ${model}
- AI user message must be a string
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/07e4cc59599ca876.
Report an issue: GitHub.