# Messages

> Reference for Message, the twelve content block types, MediaRef, AnnotationRef and TokenUsages, the typed format for all agent communication.

Source: https://10xgraph.com/docs/reference/python/messages
Last updated: 2026-10-08

A `Message` is the unit of conversation in 10xGraph: a role plus an ordered list of typed content blocks (text, images, tool calls, reasoning and more). Graph nodes, the API and the TypeScript client all exchange this shape. This page lists every field, constructor and block. For the concepts, see [State and messages](/docs/concepts/state-and-messages).

## Import paths

```python
from tenxgraph.core.state import Message
from tenxgraph.core.state.message_block import (
    TextBlock, ImageBlock, AudioBlock, VideoBlock,
    DocumentBlock, DataBlock,
    ToolCallBlock, RemoteToolCallBlock, ToolResultBlock,
    ReasoningBlock, AnnotationBlock, ErrorBlock,
    MediaRef, AnnotationRef, ContentBlock,
)
from tenxgraph.core.state.message import TokenUsages, generate_id
```

Most of these names are also exported from `tenxgraph.core.state` (everything above except `RemoteToolCallBlock` and `generate_id`).

---

## `Message`

A Pydantic model holding one conversation turn. Conversations in graph state are `list[Message]`.

### Fields

| Field | Type | Default | Description |
|---|---|---|---|
| `message_id` | `str \| int` | auto-generated | Unique message identifier. Generated by `generate_id`, which honors an ID generator registered in the dependency container. |
| `role` | `"user" \| "assistant" \| "system" \| "tool"` | **required** | Who sent the message. |
| `content` | `Sequence[ContentBlock]` | **required** | Ordered list of typed content blocks. |
| `delta` | `bool` | `False` | `True` for streaming intermediate chunks. |
| `tools_calls` | `list[dict] \| None` | `None` | Raw tool call dicts from the provider SDK. Used internally. |
| `reasoning` | `str \| None` | `None` | Reasoning trace (marked for removal in the source; use `ReasoningBlock` in `content` instead). |
| `timestamp` | `float \| None` | current time | UNIX timestamp of message creation. |
| `metadata` | `dict` | `{}` | Arbitrary key-value metadata. |
| `usages` | `TokenUsages \| None` | `None` | Token usage for this message's LLM call. |
| `raw` | `dict \| None` | `None` | The raw provider API response. |
| `parsed_content` | `dict \| BaseModel \| None` | `None` | Structured output parsed by the provider converter when the agent uses `output_schema`. Read it in preference to parsing the text block yourself. |

### Constructors and helpers

Use these class methods to build messages without assembling blocks by hand. All of them generate a `message_id` through `generate_id` when you do not pass one.

| Method | Signature | Returns |
|---|---|---|
| `text_message` | `(content: str, role="user", message_id: str \| None = None)` | `Message` with one `TextBlock`. |
| `tool_message` | `(content: Sequence[ContentBlock], message_id: str \| None = None, meta: dict \| None = None)` | `Message` with `role="tool"`. |
| `image_message` | `(*, image_url=None, image_base64=None, mime_type="image/png", text=None, role="user", message_id=None)` | `Message` with an optional text block and an `ImageBlock`. Raises `ValueError` if neither `image_url` nor `image_base64` is given. |
| `multimodal_message` | `(content_blocks: Sequence[ContentBlock], role="user", message_id=None)` | `Message` from your own blocks. |
| `from_file` | `(file_path: str, mime_type=None, text=None, role="user", message_id=None)` | `Message` with the file base64-encoded inline. Raises `FileNotFoundError`. |
| `with_image` | `async (data: bytes, mime_type: str, store, text=None, role="user", message_id=None)` | `Message` whose image is saved to a media store, keeping only a `MediaRef`. |
| `with_file` | `async (file_path: str, store, mime_type=None, text=None, role="user", message_id=None)` | Same as `from_file`, but stored through a media store. Raises `FileNotFoundError`. |

`from_file` and `with_file` pick the block type from the MIME type: `image/*` gives `ImageBlock`, `audio/*` gives `AudioBlock`, `video/*` gives `VideoBlock`, anything else gives `DocumentBlock`. If the MIME type is not passed, it is guessed from the file extension, falling back to `application/octet-stream`. The `store` argument is a media store; see [Media](/docs/reference/python/media).

```python
from tenxgraph.core.state import Message

msg = Message.text_message("Hello")
msg = Message.text_message("Hello", role="assistant")
msg = Message.text_message("Hello", role="user", message_id="msg-001")
```

#### Instance methods

| Method | Returns | Description |
|---|---|---|
| `text()` | `str` | Concatenates the text of every `TextBlock` and `ToolResultBlock` in `content`. Non-string tool outputs are JSON-encoded. Other block types are skipped. |
| `attach_media(media: MediaRef, as_type)` | `None` | Appends an `ImageBlock`, `AudioBlock`, `VideoBlock` or `DocumentBlock` to `content`. `as_type` is `"image"`, `"audio"`, `"video"` or `"document"`; anything else raises `ValueError`. `content` must be a mutable list. |

#### Direct construction

```python
from tenxgraph.core.state import Message
from tenxgraph.core.state.message_block import TextBlock

msg = Message(
    role="user",
    content=[TextBlock(text="Hello, world!")],
    metadata={"source": "api"},
)
print(msg.text())  # Hello, world!
```

---

## Content blocks

Every item in `Message.content` is one of twelve block types. They share a `type` discriminator field with a fixed literal value, so you can narrow by checking `block.type`. The union is exported as `ContentBlock`.

### `TextBlock`

```python
TextBlock(text="Hello, world!")
TextBlock(text="See reference [1].", annotations=[AnnotationRef(url="https://example.com", title="Source")])
```

| Field | Type | Description |
|---|---|---|
| `type` | `"text"` | Discriminator. |
| `text` | `str` | The text content. |
| `annotations` | `list[AnnotationRef]` | Citations or links embedded in the text. |

---

### `ImageBlock`

```python
ImageBlock(media=MediaRef(kind="url", url="https://example.com/photo.jpg", mime_type="image/jpeg"))
ImageBlock(media=MediaRef(kind="file_id", file_id="file-abc123", mime_type="image/png"))
ImageBlock(media=MediaRef(kind="data", data_base64="...", mime_type="image/png"), alt_text="Chart")
```

| Field | Type | Description |
|---|---|---|
| `type` | `"image"` | Discriminator. |
| `media` | `MediaRef` | Reference to the image. |
| `alt_text` | `str \| None` | Accessibility text. |
| `bbox` | `list[float] \| None` | Bounding box `[x1, y1, x2, y2]`. |

---

### `AudioBlock`

```python
AudioBlock(
    media=MediaRef(kind="url", url="https://example.com/recording.mp3"),
    transcript="Hello, this is a recording.",
)
```

| Field | Type | Description |
|---|---|---|
| `type` | `"audio"` | Discriminator. |
| `media` | `MediaRef` | Reference to the audio. |
| `transcript` | `str \| None` | Transcript of the audio content. |
| `sample_rate` | `int \| None` | Sample rate in Hz. |
| `channels` | `int \| None` | Number of audio channels. |

---

### `VideoBlock`

```python
VideoBlock(
    media=MediaRef(kind="url", url="https://example.com/video.mp4"),
    thumbnail=MediaRef(kind="url", url="https://example.com/thumb.jpg"),
)
```

| Field | Type | Description |
|---|---|---|
| `type` | `"video"` | Discriminator. |
| `media` | `MediaRef` | Reference to the video. |
| `thumbnail` | `MediaRef \| None` | Thumbnail image reference. |

---

### `DocumentBlock`

```python
DocumentBlock(
    media=MediaRef(kind="file_id", file_id="file-def456", mime_type="application/pdf"),
    excerpt="This report covers Q4 earnings...",
    pages=[1, 2, 3],
)
```

| Field | Type | Description |
|---|---|---|
| `type` | `"document"` | Discriminator. |
| `media` | `MediaRef` | Reference to the document file. |
| `text` | `str \| None` | Extracted full text content. |
| `pages` | `list[int] \| None` | Relevant page numbers. |
| `excerpt` | `str \| None` | Short excerpt/preview. |

---

### `DataBlock`

```python
DataBlock(mime_type="application/json", data_base64="eyJrZXkiOiAidmFsdWUifQ==")
```

| Field | Type | Description |
|---|---|---|
| `type` | `"data"` | Discriminator. |
| `mime_type` | `str` | MIME type of the data. |
| `data_base64` | `str \| None` | Base64-encoded payload. |
| `media` | `MediaRef \| None` | External media reference. |

---

### `ToolCallBlock`

Added by the agent when the LLM requests a tool call. Application code usually does not construct these directly.

```python
ToolCallBlock(id="call_abc", name="get_weather", args={"location": "London"})
```

| Field | Type | Description |
|---|---|---|
| `type` | `"tool_call"` | Discriminator. |
| `id` | `str` | Tool call ID (matches the corresponding `ToolResultBlock`). |
| `name` | `str` | Tool function name. |
| `args` | `dict` | Arguments dict. |
| `tool_type` | `str \| None` | Optional type hint e.g. `"web_search"`, `"computer_use"`. |

---

### `RemoteToolCallBlock`

Sent by the server to the TypeScript client when a tool needs to run in the browser. Do not construct manually.

| Field | Type | Description |
|---|---|---|
| `type` | `"remote_tool_call"` | Discriminator. |
| `id` | `str` | Call ID. |
| `name` | `str` | Configured remote-tool name with a matching client handler. |
| `args` | `dict` | Arguments. |
| `tool_type` | `str` | Defaults to `"remote"`. |

---

### `ToolResultBlock`

The result of a tool call execution. Constructed by `ToolNode` and returned to the LLM.

```python
ToolResultBlock(call_id="call_abc", output={"order_id": "A1001", "status": "shipped"}, is_error=False)
```

| Field | Type | Description |
|---|---|---|
| `type` | `"tool_result"` | Discriminator. |
| `call_id` | `str` | Matches the `ToolCallBlock.id` this is a response to. |
| `output` | `Any` | The result returned by the tool (string, dict, `MediaRef` or a list of blocks). |
| `is_error` | `bool` | `True` if the tool execution failed. Default `False`. |
| `status` | `"completed" \| "failed" \| None` | Status of the tool call. |

---

### `ReasoningBlock`

Exposes a reasoning model's thinking trace when the provider returns one.

| Field | Type | Description |
|---|---|---|
| `type` | `"reasoning"` | Discriminator. |
| `summary` | `str` | Summary of the reasoning. |
| `details` | `list[str] \| None` | Detailed reasoning steps. |

---

### `AnnotationBlock`

Structured annotation or citation attached to a text response.

| Field | Type | Description |
|---|---|---|
| `type` | `"annotation"` | Discriminator. |
| `kind` | `"citation" \| "note"` | Kind of annotation. Default `"citation"`. |
| `refs` | `list[AnnotationRef]` | Annotation references. |
| `spans` | `list[tuple[int, int]] \| None` | Spans covered by the annotation. |

---

### `ErrorBlock`

Represents an error that occurred during execution.

| Field | Type | Description |
|---|---|---|
| `type` | `"error"` | Discriminator. |
| `message` | `str` | Error message. |
| `code` | `str \| None` | Optional error code. |
| `data` | `dict \| None` | Additional error data. |

---

## `MediaRef`

A reference to binary media that is stored by URL, provider file ID or inline base64. The `kind` field (default `"url"`) says which of `url`, `file_id` or `data_base64` holds the content.

```python
# By URL
MediaRef(kind="url", url="https://cdn.example.com/image.png", mime_type="image/png")

# By provider file ID (OpenAI/Gemini uploaded file)
MediaRef(kind="file_id", file_id="file-abc123", mime_type="image/png")

# Inline base64 (small files only; prefer a media store for larger ones)
MediaRef(kind="data", data_base64="iVBORw0KGgo...", mime_type="image/png")
```

### Fields

| Field | Type | Description |
|---|---|---|
| `kind` | `"url" \| "file_id" \| "data"` | Reference type discriminator. |
| `url` | `str \| None` | HTTP(S) URL or `graph://media/{key}` for offloaded media. |
| `file_id` | `str \| None` | Provider-managed file ID (OpenAI Files API, Gemini File API). |
| `data_base64` | `str \| None` | Base64-encoded content. Use only for small payloads. |
| `mime_type` | `str \| None` | MIME type, e.g. `"image/jpeg"`, `"application/pdf"`. |
| `size_bytes` | `int \| None` | File size hint. |
| `sha256` | `str \| None` | Content hash for integrity verification. |
| `filename` | `str \| None` | Original filename. |
| `width` | `int \| None` | Image width in pixels. |
| `height` | `int \| None` | Image height in pixels. |
| `duration_ms` | `int \| None` | Audio/video duration in milliseconds. |
| `page` | `int \| None` | Page number for documents. |

---

## `AnnotationRef`

A pointer to the source of a citation or note, used by `TextBlock.annotations` and `AnnotationBlock.refs`.

```python
AnnotationRef(url="https://arxiv.org/abs/2303.08774", title="GPT-4 Technical Report", page=1)
```

| Field | Type | Description |
|---|---|---|
| `url` | `str \| None` | Source URL. |
| `file_id` | `str \| None` | File reference. |
| `page` | `int \| None` | Page number. |
| `index` | `int \| None` | Index position in the source. |
| `title` | `str \| None` | Display title. |

---

## `TokenUsages`

Token counts for a single LLM response, stored on `Message.usages`.

```python
usages = TokenUsages(
    completion_tokens=150,
    prompt_tokens=320,
    total_tokens=470,
)
```

| Field | Type | Default | Description |
|---|---|---|---|
| `completion_tokens` | `int` | required | Tokens generated in the response. |
| `prompt_tokens` | `int` | required | Tokens in the input prompt. |
| `total_tokens` | `int` | required | Total tokens for the call, as reported by the provider. |
| `reasoning_tokens` | `int` | `0` | Tokens used for extended reasoning (reasoning models). |
| `cache_creation_input_tokens` | `int` | `0` | Prompt cache write tokens (Anthropic-style). |
| `cache_read_input_tokens` | `int` | `0` | Prompt cache read tokens. |
| `image_tokens` | `int \| None` | `0` | Image modality tokens (multimodal models). |
| `audio_tokens` | `int \| None` | `0` | Audio modality tokens. |

---

## Inspect the blocks in a message

Check `block.type` to narrow a block to its class and read its fields. This example builds a message in memory so it runs without a model.

```python
from tenxgraph.core.state import Message
from tenxgraph.core.state.message_block import TextBlock, ToolCallBlock

msg = Message(
    role="assistant",
    content=[
        TextBlock(text="Checking the weather."),
        ToolCallBlock(id="call_1", name="get_weather", args={"location": "London"}),
    ],
)

for block in msg.content:
    if block.type == "text":
        print(block.text)
    elif block.type == "tool_call":
        print(f"Called {block.name}({block.args})")
```

With a compiled graph, the same loop works on the messages in the result: `result["messages"]` after `app.invoke(...)`. See [State](/docs/reference/python/state) for the state object.
