BerriAI/litellm · error · ValueError
Bedrock batch embedding `input` must be a string (or a…
Error message
Bedrock batch embedding `input` must be a string (or a single-element list of strings). Got type {type(candidate).__name__} for model={model}. What it means
Raised while LiteLLM converts an OpenAI /v1/embeddings batch JSONL record into a Bedrock Titan v2 InvokeModel body, which accepts exactly one string in `inputText`. After unwrapping a single-element list, the remaining value must be a Python str; anything else (dict, float, None inside a list, bytes) is rejected before any S3 upload happens. The message names the offending type and the model so you can locate the bad JSONL line.
Solutions
- Make `input` a plain JSON string, or a one-element array containing exactly one string: "input": "text to embed"
- Emit one JSONL line per embedding — multi-element string lists raise a separate error
- If you pre-tokenized the input (arrays of ints), stop: Bedrock batch only accepts raw text for Titan v2
- Lint the JSONL locally before upload (see validation snippet) so the failure surfaces before the S3 PUT is signed
Example fix
// before (JSONL line)
{"custom_id": "req_1", "url": "/v1/embeddings", "body": {"model": "amazon.titan-embed-text-v2:0", "input": {"text": "hello"}}}
// after
{"custom_id": "req_1", "url": "/v1/embeddings", "body": {"model": "amazon.titan-embed-text-v2:0", "input": "hello"}} Defensive patterns
Strategy: type-guard
Validate before calling
import json
def validate_embedding_batch(path: str) -> None:
for i, line in enumerate(open(path)):
if not line.strip():
continue
rec = json.loads(line)
inp = rec.get("body", {}).get("input")
ok = isinstance(inp, str) or (isinstance(inp, list) and len(inp) == 1 and isinstance(inp[0], str))
if not ok:
raise ValueError(f"line {i}: `input` must be a string or [string], got {inp!r}") Type guard
from typing import Any
def is_valid_bedrock_batch_input(inp: Any) -> bool:
if isinstance(inp, list):
return len(inp) == 1 and isinstance(inp[0], str) and not isinstance(inp[0], bool)
return isinstance(inp, str) Try / catch
try:
file = await client.files.create(file=batch_file, purpose="batch")
except ValueError as e:
if "batch embedding `input` must be a string" in str(e):
# extract the offending record, fix, and re-upload
log_and_repair_batch_file(e)
else:
raise Prevention
- Generate embedding JSONL with a typed writer that only emits str inputs
- Never pre-tokenize inputs for Bedrock batch — pass raw text
- Run the local JSONL lint (validationCode) in CI before any upload
When it happens
Trigger: Uploading a purpose=batch file (litellm.create_file / client.files.create) where an embedding record's `input` is not a string: e.g. {"url": "/v1/embeddings", "body": {"model": "amazon.titan-embed-text-v2:0", "input": {"text": "hi"}}} or "input": [3.14] or "input": [null]. Records without a `url` are classified as embeddings when the body has `input` but no `messages`/`prompt`, so they hit the same check.
Common situations: Reusing OpenAI embedding payloads that wrap text in nested objects; embedding pipelines that emit numbers or nulls for empty documents; hand-edited JSONL files where a quoting mistake turns the string into a number/object.
Related errors
- Batch record for /v1/completions is missing required…
- Batch record for /v1/responses is missing required `input`…
- Bedrock batch embedding currently supports only Amazon…
- contents of file are None
- Embedding batch records do not have a chat-completion…
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/38cd0e726c0aad7a.
Report an issue: GitHub.
Appendix: source
Thrown at litellm/llms/bedrock/files/transformation.py:638
"record. Got a list with "
f"{len(raw_input)} items for model={model}; emit one "
"JSONL line per input string instead."
)
else:
candidate = raw_input
# Catches pre-tokenized inputs (List[int] from OpenAI spec, or a
# single int slipping past the list-unwrap above).
# NOTE: bool is a subclass of int but treating True/False as a token
# is meaningless either way, so the broad check is fine.
if isinstance(candidate, (list, int)):
raise NotImplementedError(
"Bedrock Titan v2 batch embedding does not support "
"pre-tokenized integer inputs. Pass `input` as a string "
f"(model={model})."
)
if not isinstance(candidate, str):
raise ValueError(
"Bedrock batch embedding `input` must be a string (or a "
"single-element list of strings). Got type "
f"{type(candidate).__name__} for model={model}."
)
return candidate
def _map_openai_embedding_to_bedrock_params(
self,
openai_request_body: _OpenAIBatchRecordBody,
model: str,
) -> dict[str, object]:
"""
Transform an OpenAI /v1/embeddings request body into the
Bedrock InvokeModel `modelInput` for embedding models that AWS
supports via batch inference (CreateModelInvocationJob).
Currently routes Amazon Titan Text Embeddings V2 only; other
embedding providers (Titan G1, Titan Multimodal, Cohere Embed,View on GitHub (pinned to 77b7c6c40c)