JuliusBrussee/caveman · error
corpus provider conversion supports string or null content
Error message
corpus provider conversion supports string or null content
What it means
contentText converts a CorpusMessage's content field for provider-native request bodies (e.g. Anthropic). It accepts only JSON strings or null; any other JSON shape (arrays, numbers, objects) is rejected because the corpus schema normalizes content to plain text.
Source
Thrown at cacheengine/cachebench/corpus.go:734
return anthropicCorpusBody(provider.Model, messages)
case "bedrock":
return bedrockCorpusBody(messages)
case "gemini":
return geminiCorpusBody(messages)
default:
return nil, fmt.Errorf("unsupported corpus provider %q", provider.Provider)
}
}
func contentText(content json.RawMessage) (string, error) {
if len(content) == 0 || bytes.Equal(bytes.TrimSpace(content), []byte("null")) {
return "", nil
}
var text string
if err := json.Unmarshal(content, &text); err == nil {
return text, nil
}
return "", errors.New("corpus provider conversion supports string or null content")
}
func anthropicCorpusBody(model string, messages []CorpusMessage) ([]byte, error) {
var system []string
converted := make([]any, 0, len(messages))
for _, message := range messages {
text, err := contentText(message.Content)
if err != nil {
return nil, err
}
switch message.Role {
case "system", "developer":
if text != "" {
system = append(system, text)
}
case "assistant":
blocks := make([]any, 0, len(message.ToolCalls)+1)
if text != "" {View on GitHub (pinned to 27d5a3981a)
Solutions
- Normalize content to a string when building the corpus: join text parts of content arrays into one string (or take the first text part)
- Null non-text parts (image_url etc.) or drop those messages if text-only replay is unacceptable
- Enforce string-or-null content at corpus validation time so the failure happens at load, not provider conversion
Example fix
// before
msg.Content = json.RawMessage(`[{"type":"text","text":"hello"}]`) // triggers error
// after
text := extractTextParts(msg.Content) // "hello"
msg.Content = json.RawMessage(strconv.Quote(text)) Defensive patterns
Strategy: validation
Validate before calling
func isTextOrNullContent(raw json.RawMessage) bool {
t := bytes.TrimSpace(raw)
if len(t) == 0 || bytes.Equal(t, []byte("null")) { return true }
var s string
return json.Unmarshal(t, &s) == nil
}
for i := range corpus.Rows {
for j := range corpus.Rows[i].Messages {
if !isTextOrNullContent(corpus.Rows[i].Messages[j].Content) {
corpus.Rows[i].Messages[j].Content = normalizeToText(corpus.Rows[i].Messages[j].Content)
}
}
} Type guard
func isStringOrNullJSON(raw []byte) bool {
var s string
return json.Unmarshal(raw, &s) == nil || bytes.Equal(bytes.TrimSpace(raw), []byte("null"))
} Try / catch
if _, err := cachebench.BuildCorpusTrace(p, corpus); err != nil {
if err.Error() == "corpus provider conversion supports string or null content" {
// normalize content blocks to joined text, rebuild trace
}
} Prevention
- Flatten OpenAI multi-part content arrays into a single joined string at corpus ingest
- Enforce string-or-null content in your own corpus validator so failures surface at load time
- Document that corpora must be schema-normalized before provider-native trace building
When it happens
Trigger: A corpus message whose Content is a JSON array (OpenAI multi-part content blocks like [{"type":"text",...}]), a bare number, or an object. This surfaces during BuildCorpusTrace/anthropicCorpusBody when converting the message for a non-OpenAI provider.
Common situations: Corpus ingested from OpenAI chat logs that use multi-content arrays; user-uploaded corpora with structured content; a converter that passed raw request bodies as content instead of extracting the text.
Related errors
- message %d: %w
- cachebench: nil corpus reader
- message exceeds byte limit
- tool message requires tool_call_id
- cachebench: nil cache engine
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/6470de1a57733230.
Report an issue: GitHub.