{"record":{"id":"aaa8d98d407878ea","repo":"Budibase/budibase","slug":"ai-message-content-must-be-a-string","errorCode":null,"errorMessage":"AI message content must be a string","messagePattern":"AI message content must be a string","errorType":"http","errorClass":"HTTPError","httpStatus":422,"severity":"error","filePath":"packages/pro/src/ai/generators/tableGeneration.ts","lineNumber":180,"sourceCode":"  }\n\n  private getErrorMessage(err: unknown): string {\n    if (!err || typeof err !== \"object\") {\n      return String(err)\n    }\n    const error = err as Record<string, unknown>\n    return typeof error.message === \"string\" ? error.message : String(err)\n  }\n\n  private toPrompt(messages: Message[]): {\n    instructions?: string\n    messages: ModelMessage[]\n  } {\n    const instructions: string[] = []\n    const modelMessages: ModelMessage[] = []\n    for (const message of messages) {\n      if (typeof message.content !== \"string\") {\n        throw new HTTPError(\"AI message content must be a string\", 422)\n      }\n      if (message.role === \"system\") {\n        instructions.push(message.content)\n      } else {\n        modelMessages.push({\n          role: message.role,\n          content: message.content,\n        } as ModelMessage)\n      }\n    }\n    return {\n      instructions: instructions.join(\"\\n\\n\") || undefined,\n      messages: modelMessages,\n    }\n  }\n}\n","sourceCodeStart":162,"sourceCodeEnd":197,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/pro/src/ai/generators/tableGeneration.ts#L162-L197","documentation":"toPrompt converts an internal ModelMessage[] into prompt instructions and only supports string content. If any message's content is an array or other structured form (multimodal/parts), it throws HTTPError 422 because table generation prompts are text-only.","triggerScenarios":"Passing messages whose content is not a plain string (e.g. content arrays like [{type:\"text\",...}] from multimodal chat history or another AI pipeline) into the table generation flow, reached via the result/generate path.","commonSituations":"Reusing conversation history built for vision/multimodal models, piping messages from a different LLM SDK that structures content as parts, programmatic API usage constructing ModelMessage objects manually.","solutions":["Flatten message content to a string before calling generation: join text parts (part.text) into one string.","Strip or convert non-text content (images/tool calls) from the message history.","Normalize messages at the boundary where they are produced so all content fields are strings.","Update the generator to handle structured content if multimodal prompts are genuinely needed."],"exampleFix":"// before\nconst msgs = history.map(m => ({ role: m.role, content: m.content })) // content may be array\n// after\nconst msgs = history.map(m => ({\n  role: m.role,\n  content: typeof m.content === \"string\"\n    ? m.content\n    : m.content.map(p => p.text).join(\"\\n\"),\n}))","handlingStrategy":"validation","validationCode":"const allStrings = messages.every(m => typeof m.content === \"string\")\nif (!allStrings) throw new Error(\"Flatten multimodal message content to strings first\")","typeGuard":"function isTextMessage(m: ModelMessage): m is ModelMessage & { content: string } {\n  return typeof m.content === \"string\"\n}","tryCatchPattern":"try {\n  return await generate(req)\n} catch (e) {\n  if (e.status === 422 && e.message.includes(\"content must be a string\")) {\n    return generate(req.map(flattenContent))\n  }\n  throw e\n}","preventionTips":["Normalize message content to strings where messages are produced","Strip image/tool-call parts from histories reused across AI features","Add a shared flattenContent util for multimodal content arrays"],"tags":["ai","validation","http-422","prompt"],"backgroundTag":"message-content-type-mismatch","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}