zylon-ai/private-gpt · error · ValueError
TLDR blocks can only be used in assistant messages: {message
Error message
TLDR blocks can only be used in assistant messages: {message} What it means
Raised while validating ChatMessages: a TLDRBlock (extended content block for conversation summaries) was found in a message whose role is not 'assistant'. TLDR blocks are only meaningful as part of assistant output, so the validator enforces role == 'assistant' for any message containing one.
Source
Thrown at private_gpt/server/chat/chat_models.py:410
elif isinstance(block, ToolUseBlock):
if message.role != "assistant":
raise ValueError(
f"Tool use blocks can only be used in assistant messages: {message}"
)
if block.id in tool_uses_ids:
raise ValueError(f"Duplicate tool use ID found: {block.id}")
tool_uses_ids.add(block.id)
elif isinstance(block, ToolResultBlock):
if block.tool_use_id not in tool_uses_ids:
raise ValueError(
f"Tool result block references an unknown tool use ID: {block.tool_use_id}"
)
tool_results_ids.add(block.tool_use_id)
elif isinstance(block, TLDRBlock):
if message.role != "assistant":
raise ValueError(
f"TLDR blocks can only be used in assistant messages: {message}"
)
if tool_results_ids != tool_uses_ids:
raise ValueError(
"Tool result blocks must match the tool use IDs in the same message."
f" Found tool use IDs: {tool_uses_ids}, but tool result IDs: {tool_results_ids}"
)
return self
View on GitHub (pinned to 4a030776a3)
Solutions
- Move the TLDRBlock into an assistant-role message
- If you want to provide context/summary on the user side, send it as a plain text block instead of TLDRBlock
Example fix
# before
{"role": "user", "content": [{"type": "tldr", "text": "summary..."}]}
# after
{"role": "assistant", "content": [{"type": "tldr", "text": "summary..."}]} Defensive patterns
Strategy: type-guard
Validate before calling
for m in messages:
for b in (m.content or []):
if isinstance(b, TLDRBlock) and m.role != "assistant":
raise ValueError("move TLDRBlock to an assistant message") Type guard
def tldr_blocks_in_assistant_messages(messages: list) -> bool:
for m in messages:
role = m.get("role") if isinstance(m, dict) else m.role
blocks = m.get("content", []) if isinstance(m, dict) else (m.content or [])
if any((b.get("type") if isinstance(b, dict) else getattr(b, "type", None)) == "tldr" for b in blocks) and role != "assistant":
return False
return True Try / catch
try:
ChatMessages(messages=msgs)
except ValueError as e:
if "TLDR blocks" in str(e):
move_tldr_blocks_to_assistant(msgs)
else:
raise Prevention
- Only emit TLDR blocks on assistant turns
- Send user-side summaries as plain text blocks
When it happens
Trigger: Building a ChatMessages payload that puts a TLDRBlock into a 'user' or 'system' role message content list; e.g. a client trying to pre-seed a summary block on the user turn.
Common situations: Custom clients using the extended block protocol who assume summary blocks go with user context; copy-pasting block arrays between messages of different roles.
Related errors
- Block cannot be None: {message}
- Block cannot be None
- Duplicate tool use ID found: {block.id}
- Tool result block references an unknown tool use ID: {block.
- Tool result blocks must match the tool use IDs in the same m
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/3a4f32ecd6542a80.
Report an issue: GitHub.