run-llama/llama_index · error · ValueError

Cannot provide both content and blocks.

Error message

Cannot provide both content and blocks.

What it means

ToolOutput.__init__ accepts either a plain string content or a list of ContentBlock objects (blocks), but not both. Passing a truthy content AND a non-empty blocks list raises ValueError because the two representations would conflict; internally content is just converted to [TextBlock(text=content)].

Source

Thrown at llama-index-core/llama_index/core/tools/types.py:128

    tool_name: str
    raw_input: Dict[str, Any]
    raw_output: Any
    is_error: bool = False

    _exception: Optional[Exception] = PrivateAttr(default=None)

    def __init__(
        self,
        tool_name: str,
        content: Optional[str] = None,
        blocks: Optional[List[ContentBlock]] = None,
        raw_input: Optional[Dict[str, Any]] = None,
        raw_output: Optional[Any] = None,
        is_error: bool = False,
        exception: Optional[Exception] = None,
    ):
        if content and blocks:
            raise ValueError("Cannot provide both content and blocks.")
        if content:
            blocks = [TextBlock(text=content)]
        elif blocks:
            pass
        else:
            blocks = []

        super().__init__(
            tool_name=tool_name,
            blocks=blocks,
            raw_input=raw_input,
            raw_output=raw_output,
            is_error=is_error,
        )

        self._exception = exception

    @property

View on GitHub (pinned to afd0fef371)

Solutions

  1. Pass only one form: either content='text' or blocks=[TextBlock(...), ImageBlock(...)].
  2. If you need a text summary plus blocks, embed it as a TextBlock inside blocks.
  3. In wrapper/generic code, choose blocks when the caller supplied blocks, else content.

Example fix

# before
 out = ToolOutput(
     tool_name='search',
     content='hello',
     blocks=[TextBlock(text='hello')],  # both supplied -> ValueError
 )

# after
 out = ToolOutput(tool_name='search', blocks=[TextBlock(text='hello')])
Defensive patterns

Strategy: validation

Validate before calling

def build_tool_output(tool_name: str, content=None, blocks=None):
    if content and blocks:
        raise TypeError('Pass content OR blocks, not both')
    if content:
        return ToolOutput(tool_name=tool_name, content=content)
    return ToolOutput(tool_name=tool_name, blocks=blocks or [])

Type guard

def has_conflicting_output_args(content, blocks) -> bool:
    return bool(content) and bool(blocks)

Try / catch

try:
    out = ToolOutput(tool_name=name, content=c, blocks=b)
except ValueError as e:
    if 'both content and blocks' in str(e) and b:
        out = ToolOutput(tool_name=name, blocks=b)  # blocks win
    else:
        raise

Prevention

When it happens

Trigger: Constructing ToolOutput(tool_name='x', content='result text', blocks=[TextBlock(text='result text')]) or subclassing ToolOutput and forwarding both parameters. Also hit by wrapper code that adds a default content string while passing through caller-supplied blocks.

Common situations: Custom tool implementations building rich (multi-block) outputs while keeping a legacy content argument 'just in case'; refactors from string outputs to block outputs that forget to remove the old content kwarg.

Related errors


AI-assisted analysis of run-llama/llama_index@afd0fef371 (2026-08-15). Data as JSON: /api/errors/64981f8780914134. Report an issue: GitHub.