> ## Documentation Index
> Fetch the complete documentation index at: https://developers.reflection.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Beam Harmony format

> How Beam's chat template formats conversations and how its response template parses output

Beam Harmony is the text format Beam reads and writes. It builds on OpenAI's [Harmony format](https://developers.openai.com/cookbook/articles/openai-harmony) for gpt-oss, with several changes listed in [Differences from OpenAI Harmony](#differences-from-openai-harmony).

You need this page if you run the Beam checkpoint yourself, fine-tune it, or parse its raw output. The Reflection API handles the format for you: see [Reasoning](/reasoning) and [Tool calling](/tool-calling) instead.

The checkpoint ships two templates:

* `chat_template.jinja` renders messages into a prompt.
* `response_template`, in `tokenizer_config.json`, parses generated text back into a message: the reasoning goes to `thinking`, the answer to `content`, and tool calls to `tool_calls`.

<Warning>
  Preserve the reasoning of every earlier assistant turn in the conversation history. Beam's output degrades without it, so set `allow_missing_thinking=False` when you serve the model. See [Reasoning across turns](#reasoning-across-turns).
</Warning>

## Run the examples

Each section below has a collapsed example you can run. The examples use the tokenizer from the Beam checkpoint. They don't load model weights or need a GPU.

Install Transformers and set `BEAM_MODEL_ID` to the checkpoint's Hugging Face repo ID or a local path. Every example builds on this setup. It defines `render`, which renders a prompt for generation, and two inputs the examples reuse: `hello`, a one-message conversation, and `get_weather`, a sample tool.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import os
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained(os.environ["BEAM_MODEL_ID"])


def render(messages, **kwargs):
    return tokenizer.apply_chat_template(
        messages, tokenize=False, add_generation_prompt=True, **kwargs
    )


hello = [{"role": "user", "content": "Hello!"}]

get_weather = {
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get the current weather for a city.",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {"type": "string"},
                "days": {"type": "integer", "description": "Forecast length in days"},
            },
            "required": ["city"],
        },
    },
}
```

## Messages

Each message starts with `<|start|>` and the speaker, and ends with `<|end|>`, `<|call|>`, or `<|return|>`:

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
<|start|>user<|message|>What is 2 + 2?<|end|>
<|start|>assistant<|channel|>analysis<|message|>Simple arithmetic.<|end|>
<|start|>assistant<|channel|>final<|message|>4<|return|>
```

| Marker | Meaning |
| - | - |
| `<\|start\|>` | Starts a message. The speaker follows: `system`, `developer`, `user`, `assistant`, or a tool name. |
| `<\|channel\|>` | Names the channel: `analysis` for reasoning and tool traffic, `final` for the answer. |
| `<\|constrain\|>` | Names the argument format of a tool call, always `xml`. |
| `<\|message\|>` | Starts the message body. |
| `<\|end\|>` | Ends a message. The turn continues. |
| `<\|call\|>` | Ends a tool call and hands control to the tool. Stops generation. |
| `<\|return\|>` | Ends the final answer. Stops generation. |

Each marker is a single token in Beam's vocabulary, but only `<|return|>`, the end-of-sequence token, is a special token. Decoding with `skip_special_tokens=True` keeps the other markers, so the parser still sees message boundaries. Channel names, recipients such as `to=get_weather`, and `rfl:parameter` tags are plain text.

The rendered prompt has no line breaks between messages; the examples on this page add them for readability.

## Chat template

### System message

The template writes the system message itself. By default:

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
<|start|>system<|message|>You are Beam, an AI assistant trained by Reflection.

Reasoning effort: 512

# Valid channels: analysis, final. Channel must be included for every message.<|end|>
```

`model_identity` replaces the first line, and `reasoning_effort` sets the effort line. [Built-in tools](#built-in-tools) are declared before the channels line.

<Accordion title="Example: render a prompt">
  ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  print(render(hello))
  ```

  ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  <|start|>system<|message|>You are Beam, an AI assistant trained by Reflection.

  Reasoning effort: 512

  # Valid channels: analysis, final. Channel must be included for every message.<|end|><|start|>user<|message|>Hello!<|end|><|start|>assistant
  ```
</Accordion>

### Reasoning effort

The effort is an integer. Pass one directly, or use a label:

| Label | Effort |
| - | - |
| `minimal` | 8 |
| `low` | 32 |
| `medium` | 64 |
| `high` | 128 |
| `xhigh` | 256 |
| `max` | 512 |

`minimal` through `xhigh` follow the names of OpenAI's `reasoning_effort` API parameter, and `max` is Beam's highest effort. If you leave `reasoning_effort` unset or pass `None`, the template uses 512. Any other string raises an error, including OpenAI's `"none"`: Beam always reasons, so it has no effort level that turns reasoning off. Beam wasn't trained below 8, so smaller values aren't respected.

<Accordion title="Example: set the reasoning effort">
  ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  for effort in ["low", 100, None]:
      prompt = render(hello, reasoning_effort=effort)
      print(next(line for line in prompt.splitlines() if line.startswith("Reasoning effort")))
  ```

  ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  Reasoning effort: 32
  Reasoning effort: 100
  Reasoning effort: 512
  ```

  Strings other than the six labels raise an error:

  ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  render(hello, reasoning_effort="none")
  ```

  ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  TemplateError: reasoning_effort must be an int or one of: minimal, low, medium, high, xhigh, max
  ```
</Accordion>

### Instructions

`system` and `developer` messages are equivalent. Consecutive ones at the start of the conversation are joined, a blank line apart, under `# Instructions` in a developer message:

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
<|start|>developer<|message|># Instructions

{first message}

{second message}<|end|>
```

<Note>
  A `system` or `developer` message that comes after a message with another role isn't rendered. Put all `system` and `developer` messages at the start of the conversation.
</Note>

### Tools

Tools passed in `tools` are declared as TypeScript under `# Tools`, after the instructions:

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
<|start|>developer<|message|># Tools

// Get the current weather for a city.
type get_weather = (_: {
city: string,
// Forecast length in days
days?: number,
}) => any;<|end|>
```

Descriptions become `//` comments, optional parameters get `?`, and `integer` becomes `number`. Tool names are used as is, with no `functions.` namespace.

<Accordion title="Example: declare tools">
  This schema covers the JSON Schema features the template renders:

  ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  search_flights = {
      "type": "function",
      "function": {
          "name": "search_flights",
          "description": "Search for flights between two airports.",
          "parameters": {
              "type": "object",
              "properties": {
                  "origin": {"type": "string", "description": "IATA airport code", "examples": ["JFK", "LHR"]},
                  "destination": {"type": "string", "description": "IATA airport code"},
                  "depart": {"type": "string", "description": "Departure date, YYYY-MM-DD"},
                  "return_date": {"type": ["string", "null"]},
                  "passengers": {"type": "integer", "default": 1},
                  "cabin": {"type": "string", "enum": ["economy", "business", "first"], "default": "economy"},
                  "airlines": {"type": "array", "items": {"type": "string"}},
                  "budget": {
                      "type": "object",
                      "properties": {"max_price": {"type": "number"}, "currency": {"type": "string"}},
                      "required": ["max_price"],
                  },
              },
              "required": ["origin", "destination", "depart"],
          },
      },
  }
  prompt = render(hello, tools=[search_flights])
  print(prompt[prompt.index("<|start|>developer"):prompt.index("<|start|>user")])
  ```

  ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  <|start|>developer<|message|># Tools

  // Search for flights between two airports.
  type search_flights = (_: {
  // IATA airport code
  // Examples:
  // - "JFK"
  // - "LHR"
  origin: string,
  // IATA airport code
  destination: string,
  // Departure date, YYYY-MM-DD
  depart: string,
  return_date?: string | null,
  passengers?: number, // default: 1
  cabin?: "economy" | "business" | "first", // default: economy
  airlines?: string[],
  budget?: {
      max_price: number,
      currency?: string,
      },
  }) => any;<|end|>
  ```

  Parameters keep the order of `properties`.
</Accordion>

#### Built-in tools

`builtin_tools` moves the named tools from `tools` to the system message, with their own definitions. Only name `search` and `browse`, and only with the definitions Beam was trained with, shown in the example below. Every other tool, including a `search` or `browse` with a different definition, belongs in the developer message, where `tools` declares it by default.

<Warning>
  A `bash` tool whose only parameter is a `command` string is always treated as built in. The template declares it with the definition Beam was trained with, in place of yours:

  ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  # Tools

  // Execute a bash command. The output contains the command's stdout and stderr.
  // Commands time out after 10 minutes. You can set a shorter timeout within the bash command.
  type bash = (_: {
  // The command to execute
  command: string,
  }) => any;
  ```

  A `bash` tool with other parameters, or with constraints on `command` such as an `enum`, stays in the developer message with your definition.
</Warning>

<AccordionGroup>
  <Accordion title="Example: built-in search and browse">
    These are the `search` and `browse` definitions Beam was trained with:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    search = {
        "type": "function",
        "function": {
            "name": "search",
            "description": "Search for content on the internet using a search engine. The search results include the website title, address (URL), and description.",
            "parameters": {
                "type": "object",
                "properties": {"query": {"type": "string", "description": "The search query."}},
                "required": ["query"],
            },
        },
    }
    browse = {
        "type": "function",
        "function": {
            "name": "browse",
            "description": "Visit a webpage and inspect its content. Pass only a URL to fetch/cache the page and return an approximately 8000-token preview; include a regex pattern to search the full cached page content without a sandbox. Search results are also capped around 8000 tokens.",
            "parameters": {
                "type": "object",
                "properties": {
                    "url": {"type": "string", "description": "The website address (URL) of the content to be obtained, which can usually be obtained from search results. The page is cached by URL for later searches on this tool instance."},
                    "pattern": {"type": "string", "description": "Optional regular expression pattern. If omitted, the tool fetches and caches the page while returning a capped preview; if provided, it searches the full cached page text."},
                    "output_mode": {"type": "string", "enum": ["content", "count"], "default": "content", "description": "Search output mode: content returns matching lines; count returns only the number of matching lines."},
                    "ignore_case": {"type": "boolean", "default": False, "description": "Search without matching letter case."},
                    "context_lines": {"type": "integer", "default": 0, "description": "Number of lines before and after each matching line."},
                    "multiline": {"type": "boolean", "default": False, "description": "Allow the regular expression to match across line breaks."},
                    "max_lines": {"type": "integer", "default": 250, "description": "Maximum search output lines to return after offset. Pass 0 for unlimited lines up to output caps."},
                    "offset": {"type": "integer", "default": 0, "description": "Skip first N search output lines before applying max_lines."},
                },
                "required": ["url"],
            },
        },
    }
    prompt = render(hello, tools=[search, browse], builtin_tools=["search", "browse"])
    print(prompt[: prompt.index("<|start|>user")])
    ```

    ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    <|start|>system<|message|>You are Beam, an AI assistant trained by Reflection.

    Reasoning effort: 512

    # Tools

    // Search for content on the internet using a search engine. The search results include the website title, address (URL), and description.
    type search = (_: {
    // The search query.
    query: string,
    }) => any;

    // Visit a webpage and inspect its content. Pass only a URL to fetch/cache the page and return an approximately 8000-token preview; include a regex pattern to search the full cached page content without a sandbox. Search results are also capped around 8000 tokens.
    type browse = (_: {
    // The website address (URL) of the content to be obtained, which can usually be obtained from search results. The page is cached by URL for later searches on this tool instance.
    url: string,
    // Optional regular expression pattern. If omitted, the tool fetches and caches the page while returning a capped preview; if provided, it searches the full cached page text.
    pattern?: string,
    // Search output mode: content returns matching lines; count returns only the number of matching lines.
    output_mode?: "content" | "count", // default: content
    // Search without matching letter case.
    ignore_case?: boolean, // default: false
    // Number of lines before and after each matching line.
    context_lines?: number, // default: 0
    // Allow the regular expression to match across line breaks.
    multiline?: boolean, // default: false
    // Maximum search output lines to return after offset. Pass 0 for unlimited lines up to output caps.
    max_lines?: number, // default: 250
    // Skip first N search output lines before applying max_lines.
    offset?: number, // default: 0
    }) => any;

    # Valid channels: analysis, final. Channel must be included for every message.<|end|>
    ```
  </Accordion>

  <Accordion title="Example: the bash tool">
    The first render passes only `command`, so the template replaces the tool with Beam's own `bash` definition, in the system message. The second adds a `timeout` parameter, so the tool stays in the developer message as written, with its own description and both parameters:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    def bash_tool(**extra_params):
        return {
            "type": "function",
            "function": {
                "name": "bash",
                "description": "Run a shell command in the sandbox.",
                "parameters": {
                    "type": "object",
                    "properties": {"command": {"type": "string", "description": "Shell command"}, **extra_params},
                    "required": ["command"],
                },
            },
        }

    print(render(hello, tools=[bash_tool()]))
    print(render(hello, tools=[bash_tool(timeout={"type": "integer"})]))
    ```

    ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    <|start|>system<|message|>You are Beam, an AI assistant trained by Reflection.

    Reasoning effort: 512

    # Tools

    // Execute a bash command. The output contains the command's stdout and stderr.
    // Commands time out after 10 minutes. You can set a shorter timeout within the bash command.
    type bash = (_: {
    // The command to execute
    command: string,
    }) => any;

    # Valid channels: analysis, final. Channel must be included for every message.<|end|><|start|>user<|message|>Hello!<|end|><|start|>assistant
    <|start|>system<|message|>You are Beam, an AI assistant trained by Reflection.

    Reasoning effort: 512

    # Valid channels: analysis, final. Channel must be included for every message.<|end|><|start|>developer<|message|># Tools

    // Run a shell command in the sandbox.
    type bash = (_: {
    // Shell command
    command: string,
    timeout?: number,
    }) => any;<|end|><|start|>user<|message|>Hello!<|end|><|start|>assistant
    ```
  </Accordion>
</AccordionGroup>

### Assistant turns

An assistant turn starts with one `analysis` message that holds the reasoning. It then ends with either a `final` answer and `<|return|>`, or one or more tool calls and `<|call|>`. After tool results come back, the next turn starts with `analysis` again. Beam never writes an empty analysis message.

In history, put reasoning in `reasoning_content` and the answer in `content`. The template also reads reasoning from `thinking`, the field the [response template](#response-template) returns, and from `reasoning`, so a parsed message can go back into the history as is. Both take plain text: the template raises an error if they contain Harmony channel headers.

### Reasoning across turns

Beam's output degrades when earlier reasoning is missing. Many OpenAI-compatible clients drop `reasoning_content` from the history they send back, so check that yours keeps it.

By default, the template renders an assistant turn without reasoning as an empty analysis message. With `allow_missing_thinking=False` and `add_generation_prompt=True`, it raises an error instead if any earlier assistant turn has empty or missing reasoning:

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
TemplateError: The assistant turn at messages[1] is missing its reasoning. Dropping reasoning from earlier turns degrades this model's output. Send it back in 'reasoning_content', or set allow_missing_thinking=true to skip this check.
```

When you serve Beam, set `allow_missing_thinking=False` so that clients which drop reasoning get an error instead of degraded output. On SGLang and vLLM, start the server with `--default-chat-template-kwargs '{"allow_missing_thinking": false}'`. TokenSpeed has no server-wide default, so send `"chat_template_kwargs": {"allow_missing_thinking": false}` with each request.

The check doesn't run without `add_generation_prompt`, so rendering complete conversations for training works either way.

<Accordion title="Example: send reasoning back">
  By default, a history whose assistant turn has no reasoning renders an empty analysis message:

  ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  history = [
      {"role": "user", "content": "Hi"},
      {"role": "assistant", "content": "Hello!"},
      {"role": "user", "content": "How are you?"},
  ]
  prompt = render(history)
  print(prompt[prompt.index("<|start|>user"):])
  ```

  ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  <|start|>user<|message|>Hi<|end|><|start|>assistant<|channel|>analysis<|message|><|end|><|start|>assistant<|channel|>final<|message|>Hello!<|return|><|start|>user<|message|>How are you?<|end|><|start|>assistant
  ```

  With `allow_missing_thinking=False`, the same history raises an error:

  ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  render(history, allow_missing_thinking=False)
  ```

  ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  TemplateError: The assistant turn at messages[1] is missing its reasoning. Dropping reasoning from earlier turns degrades this model's output. Send it back in 'reasoning_content', or set allow_missing_thinking=true to skip this check.
  ```

  With the reasoning restored, it renders as an `analysis` message before the answer:

  ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  history[1]["reasoning_content"] = "The user is greeting me."
  prompt = render(history, allow_missing_thinking=False)
  print(prompt[prompt.index("<|start|>user"):])
  ```

  ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  <|start|>user<|message|>Hi<|end|><|start|>assistant<|channel|>analysis<|message|>The user is greeting me.<|end|><|start|>assistant<|channel|>final<|message|>Hello!<|return|><|start|>user<|message|>How are you?<|end|><|start|>assistant
  ```
</Accordion>

### Tool calls

A tool call is an assistant message addressed to the tool, with XML arguments. The result comes back from the tool, addressed to the assistant:

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
<|start|>assistant to=get_weather<|channel|>analysis <|constrain|>xml<|message|><rfl:parameter name="city">
London
</rfl:parameter><|call|>
<|start|>get_weather to=assistant<|channel|>analysis<|message|>14 C, cloudy<|end|>
```

* `function.arguments` must be a dict. Parse JSON strings before rendering.
* String values are written as is. Other values are written as JSON.
* A value can't contain `</rfl:parameter>`.
* The tool message needs a `name`, or a `tool_call_id` that matches an earlier call.
* Report errors in the tool message's `content`, like any other result.

<Accordion title="Example: render a tool loop">
  ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  messages = [
      {"role": "user", "content": "What's the weather in London?"},
      {
          "role": "assistant",
          "reasoning_content": "I should check the weather tool.",
          "tool_calls": [{
              "id": "call_1",
              "type": "function",
              "function": {"name": "get_weather", "arguments": {"city": "London"}},
          }],
      },
      {"role": "tool", "tool_call_id": "call_1", "content": "14 C, cloudy"},
  ]
  prompt = render(messages, tools=[get_weather])
  print(prompt[prompt.index("<|start|>assistant"):])
  ```

  ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  <|start|>assistant<|channel|>analysis<|message|>I should check the weather tool.<|end|><|start|>assistant to=get_weather<|channel|>analysis <|constrain|>xml<|message|><rfl:parameter name="city">
  London
  </rfl:parameter><|call|><|start|>get_weather to=assistant<|channel|>analysis<|message|>14 C, cloudy<|end|><|start|>assistant
  ```

  The tool message only has a `tool_call_id`, so the template takes the speaker name, `get_weather`, from the matching call. `call_1` itself doesn't appear in the prompt.
</Accordion>

#### Parallel calls

Each call is a separate message. All but the last end with `<|end|>`, and the last ends with `<|call|>`:

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
<|start|>assistant to=get_weather<|channel|>analysis <|constrain|>xml<|message|><rfl:parameter name="city">
London
</rfl:parameter><|end|>
<|start|>assistant to=get_weather<|channel|>analysis <|constrain|>xml<|message|><rfl:parameter name="city">
Paris
</rfl:parameter><|call|>
<|start|>get_weather to=assistant<|channel|>analysis<|message|>14 C, cloudy<|end|>
<|start|>get_weather to=assistant<|channel|>analysis<|message|>18 C, sunny<|end|>
```

<Warning>
  Return tool results in the order of the calls. Call IDs aren't part of the format, so Beam matches results to calls by position. Results in the wrong order render without an error, but Beam then reads each result as the answer to a different call.
</Warning>

<Accordion title="Example: parallel calls">
  ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  messages = [
      {"role": "user", "content": "Compare the weather in London and Paris."},
      {
          "role": "assistant",
          "reasoning_content": "I need both cities.",
          "tool_calls": [
              {"id": "call_1", "type": "function", "function": {"name": "get_weather", "arguments": {"city": "London"}}},
              {"id": "call_2", "type": "function", "function": {"name": "get_weather", "arguments": {"city": "Paris", "days": 3}}},
          ],
      },
      {"role": "tool", "tool_call_id": "call_1", "content": "14 C, cloudy"},
      {"role": "tool", "tool_call_id": "call_2", "content": "18 C, sunny"},
  ]
  prompt = render(messages, tools=[get_weather])
  print(prompt[prompt.index("<|start|>assistant"):])
  ```

  ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  <|start|>assistant<|channel|>analysis<|message|>I need both cities.<|end|><|start|>assistant to=get_weather<|channel|>analysis <|constrain|>xml<|message|><rfl:parameter name="city">
  London
  </rfl:parameter><|end|><|start|>assistant to=get_weather<|channel|>analysis <|constrain|>xml<|message|><rfl:parameter name="city">
  Paris
  </rfl:parameter>
  <rfl:parameter name="days">
  3
  </rfl:parameter><|call|><|start|>get_weather to=assistant<|channel|>analysis<|message|>14 C, cloudy<|end|><|start|>get_weather to=assistant<|channel|>analysis<|message|>18 C, sunny<|end|><|start|>assistant
  ```

  Both results come from `get_weather`, so only their order tells Beam which city each one answers.
</Accordion>

### Template arguments

| Argument | Default | Effect |
| - | - | - |
| `reasoning_effort` | `max` (512) | Label or integer. See [Reasoning effort](#reasoning-effort). |
| `model_identity` | `You are Beam, an AI assistant trained by Reflection.` | First line of the system message. |
| `builtin_tools` | None | Names of tools from `tools` to declare in the system message. Only for `search` and `browse`; see [Built-in tools](#built-in-tools). |
| `allow_missing_thinking` | `True` | Set to `False` to raise an error when an earlier assistant turn has no reasoning. Recommended for serving; see [Reasoning across turns](#reasoning-across-turns). |

`tools` and `add_generation_prompt` work as with any chat template.

### Training masks

The assistant's output is wrapped in a `{% generation %}` block, so `apply_chat_template(..., return_dict=True, return_assistant_tokens_mask=True)` returns `assistant_masks`. The mask covers each assistant turn from its first `<|channel|>` through `<|return|>` or `<|call|>`. TRL's `SFTConfig(assistant_only_loss=True)` uses this mask.

<Accordion title="Example: build a training mask">
  ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  conversation = [
      {"role": "user", "content": "What is 2 + 2?"},
      {"role": "assistant", "reasoning_content": "Simple arithmetic.", "content": "4"},
  ]
  encoded = tokenizer.apply_chat_template(
      conversation, tokenize=True, return_dict=True, return_assistant_tokens_mask=True
  )
  trained_ids = [i for i, m in zip(encoded["input_ids"], encoded["assistant_masks"]) if m]
  print(tokenizer.decode(trained_ids))
  ```

  ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  <|channel|>analysis<|message|>Simple arithmetic.<|end|><|start|>assistant<|channel|>final<|message|>4<|return|>
  ```
</Accordion>

## Response template

A response template is the output-side counterpart of a chat template: it describes how to turn generated text back into a message. It is a recent Transformers standard; see [Response parsing](https://huggingface.co/docs/transformers/main/en/chat_response_parsing).

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
prompt = tokenizer.apply_chat_template(
    messages, tools=tools, tokenize=False, add_generation_prompt=True
)
message = tokenizer.parse_response(generated, prefix=prompt, tools=tools)
```

* `generated` is the text the model wrote after `prompt`.
* `prefix` is the prompt you generated from. The parser needs it because the prompt ends with an open `<|start|>assistant` header.
* `tools` lets the parser cast arguments to the types in each tool's schema. Without it, every argument is a string.
* Parsed tool calls have no `id`. Assign your own before running the tools.

For streaming, `tokenizer.get_response_parser(prefix=prompt, tools=tools)` returns a parser that you `feed()` text to as it arrives.

These examples parse hand-written output. With a real model, parse the decoded new tokens instead.

<AccordionGroup>
  <Accordion title="Example: parse an answer">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    prompt = render(hello)
    generated = (
        "<|channel|>analysis<|message|>The user is greeting me.<|end|>"
        "<|start|>assistant<|channel|>final<|message|>Hi! How can I help?<|return|>"
    )
    print(tokenizer.parse_response(generated, prefix=prompt))
    ```

    ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    {'role': 'assistant', 'thinking': 'The user is greeting me.', 'content': 'Hi! How can I help?'}
    ```
  </Accordion>

  <Accordion title="Example: parse a tool call">
    Without `tools`, every argument is a string. With `tools`, arguments are cast to the schema's types:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    prompt = render([{"role": "user", "content": "3-day forecast for Paris?"}], tools=[get_weather])
    generated = (
        "<|channel|>analysis<|message|>I'll call the weather tool.<|end|>"
        "<|start|>assistant to=get_weather<|channel|>analysis <|constrain|>xml<|message|>"
        '<rfl:parameter name="city">\nParis\n</rfl:parameter>\n'
        '<rfl:parameter name="days">\n3\n</rfl:parameter><|call|>'
    )
    print(tokenizer.parse_response(generated, prefix=prompt)["tool_calls"])
    print(tokenizer.parse_response(generated, prefix=prompt, tools=[get_weather])["tool_calls"])
    ```

    ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    [{'type': 'function', 'function': {'name': 'get_weather', 'arguments': {'city': 'Paris', 'days': '3'}}}]
    [{'type': 'function', 'function': {'name': 'get_weather', 'arguments': {'city': 'Paris', 'days': 3}}}]
    ```

    Parallel calls come back as one entry each, in order:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    generated = (
        "<|channel|>analysis<|message|>I need both cities.<|end|>"
        "<|start|>assistant to=get_weather<|channel|>analysis <|constrain|>xml<|message|>"
        '<rfl:parameter name="city">\nLondon\n</rfl:parameter><|end|>'
        "<|start|>assistant to=get_weather<|channel|>analysis <|constrain|>xml<|message|>"
        '<rfl:parameter name="city">\nParis\n</rfl:parameter><|call|>'
    )
    for call in tokenizer.parse_response(generated, prefix=prompt, tools=[get_weather])["tool_calls"]:
        print(call["function"])
    ```

    ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    {'name': 'get_weather', 'arguments': {'city': 'London'}}
    {'name': 'get_weather', 'arguments': {'city': 'Paris'}}
    ```
  </Accordion>

  <Accordion title="Example: parse a stream">
    `get_response_parser` returns a parser that you feed text as it arrives. It emits `region_open`, `region_chunk`, and `region_close` events:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    prompt = render(hello)
    chunks = [
        "<|channel|>analysis<|message|>",
        "The user is ",
        "greeting me.",
        "<|end|><|start|>assistant<|channel|>final<|message|>",
        "Hi! How ",
        "can I help?",
        "<|return|>",
    ]
    parser = tokenizer.get_response_parser(prefix=prompt)
    for chunk in chunks:
        for event in parser.feed(chunk):
            if event["type"] == "region_chunk":
                print(event["field"], repr(event["text"]))
    message, _ = parser.finalize()
    print(message)
    ```

    ```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    thinking 'The user is '
    thinking 'greeting me.'
    content 'Hi! How '
    content 'can I help?'
    {'role': 'assistant', 'thinking': 'The user is greeting me.', 'content': 'Hi! How can I help?'}
    ```
  </Accordion>
</AccordionGroup>

## Differences from OpenAI Harmony

| | OpenAI Harmony | Beam Harmony |
| - | - | - |
| Channels | `analysis`, `commentary`, `final` | `analysis`, `final` |
| Tool calls | `commentary` channel, JSON arguments | `analysis` channel, XML arguments |
| Tool names | `functions.` namespace | Bare names |
| Reasoning effort | A label, such as `Reasoning: high` | An integer, such as `Reasoning effort: 128`. [Labels](#reasoning-effort) map to integers. |

* **XML arguments:** JSON escaping interacts poorly with code in tool arguments, which XML tags avoid.
* **No commentary channel:** Beam doesn't write tool preambles or other commentary, so tool calls and results use the `analysis` channel.
* **Required reasoning:** Beam was trained to reason on every turn, so each assistant turn starts with a non-empty `analysis` message.

Beam also has no `<|refusal|>` marker.
