Skip to main content
Beam Harmony is the text format Beam reads and writes. It builds on OpenAI’s Harmony format for gpt-oss, with several changes listed in 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 and 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.
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.

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.

Messages

Each message starts with <|start|> and the speaker, and ends with <|end|>, <|call|>, or <|return|>:
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:
model_identity replaces the first line, and reasoning_effort sets the effort line. Built-in tools are declared before the channels line.

Reasoning effort

The effort is an integer. Pass one directly, or use a label: 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.
Strings other than the six labels raise an error:

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:
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.

Tools

Tools passed in tools are declared as TypeScript under # Tools, after the instructions:
Descriptions become // comments, optional parameters get ?, and integer becomes number. Tool names are used as is, with no functions. namespace.
This schema covers the JSON Schema features the template renders:
Parameters keep the order of properties.

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.
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:
A bash tool with other parameters, or with constraints on command such as an enum, stays in the developer message with your definition.
These are the search and browse definitions Beam was trained with:
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:

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 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:
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.
By default, a history whose assistant turn has no reasoning renders an empty analysis message:
With allow_missing_thinking=False, the same history raises an error:
With the reasoning restored, it renders as an analysis message before the answer:

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:
  • 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.
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.

Parallel calls

Each call is a separate message. All but the last end with <|end|>, and the last ends with <|call|>:
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.
Both results come from get_weather, so only their order tells Beam which city each one answers.

Template arguments

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.

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.
  • 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.
Without tools, every argument is a string. With tools, arguments are cast to the schema’s types:
Parallel calls come back as one entry each, in order:
get_response_parser returns a parser that you feed text as it arrives. It emits region_open, region_chunk, and region_close events:

Differences from OpenAI Harmony

  • 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.
Last modified on October 16, 2026