Instructions to use trl-internal-testing/tiny-BeamForCausalLM with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use trl-internal-testing/tiny-BeamForCausalLM with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="trl-internal-testing/tiny-BeamForCausalLM") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("trl-internal-testing/tiny-BeamForCausalLM") model = AutoModelForCausalLM.from_pretrained("trl-internal-testing/tiny-BeamForCausalLM", device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=256) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use trl-internal-testing/tiny-BeamForCausalLM with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "trl-internal-testing/tiny-BeamForCausalLM" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "trl-internal-testing/tiny-BeamForCausalLM", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/trl-internal-testing/tiny-BeamForCausalLM
- SGLang
How to use trl-internal-testing/tiny-BeamForCausalLM with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "trl-internal-testing/tiny-BeamForCausalLM" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "trl-internal-testing/tiny-BeamForCausalLM", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "trl-internal-testing/tiny-BeamForCausalLM" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "trl-internal-testing/tiny-BeamForCausalLM", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use trl-internal-testing/tiny-BeamForCausalLM with Docker Model Runner:
docker model run hf.co/trl-internal-testing/tiny-BeamForCausalLM
Download chat_template.jinja from trl-internal-testing/tiny-BeamForCausalLM: direct link, hf CLI and curl.
- Browser
- Download file 23.8 kB
-
https://huggingface.co/trl-internal-testing/tiny-BeamForCausalLM/resolve/main/chat_template.jinja
- Command line
-
hf download hf://trl-internal-testing/tiny-BeamForCausalLM/chat_template.jinja
-
curl -L -o chat_template.jinja https://huggingface.co/trl-internal-testing/tiny-BeamForCausalLM/resolve/main/chat_template.jinja
23.8 kB
| {#- | |
| harmony_xml: Harmony wire format with XML tool-call bodies. | |
| Channels are `analysis` and `final`; there is no commentary channel. Tools | |
| are declared as flat TypeScript under `# Tools` and called with | |
| `<|constrain|>xml` and `rfl:parameter` bodies. | |
| Kwargs: | |
| - model_identity: defaults to "You are Beam, an AI assistant trained by | |
| Reflection." | |
| - reasoning_effort: emits `Reasoning effort: N` after the identity line; | |
| defaults to 512 (`max`) when unset or null. Accepts an int, or the labels | |
| `minimal`/`low`/`medium`/`high`/`xhigh`/`max` mapped to | |
| 8/32/64/128/256/512. Other strings raise. `minimal` is 8, the smallest | |
| effort training sampled: a smaller budget is not respected. | |
| - builtin_tools: names from `tools=` to render in the system `# Tools` block, | |
| with their own definitions. Only name `search` and `browse`, and only with | |
| the definitions Beam was trained with. Every other tool, including a | |
| `search` or `browse` with a different definition, belongs in the developer | |
| block, where `tools=` renders it by default. | |
| Exception: a `bash` tool whose only parameter is an unconstrained `command` | |
| string is always declared in the system block with the trained definition, | |
| without this kwarg; its own descriptions are not rendered. A `bash` with | |
| any other call shape follows the rule above. | |
| - allow_missing_thinking: defaults to true. When false, a generation prompt | |
| raises when any assistant turn has empty or no reasoning, since dropping it | |
| degrades the model's output. Setting it to false is highly recommended for | |
| serving. | |
| OpenAI roles "system" and "developer" are equivalent and map to the developer | |
| `# Instructions` block. Consecutive ones at the start of `messages` join into | |
| that one block, a blank line apart; empty content is omitted. A system or | |
| developer message after the first message of another role is not rendered. | |
| The Reflection system block is injected before the developer block. | |
| Tool wire: | |
| - Calls: `to={name}` + analysis + `<|constrain|>xml` + rfl: body | |
| - Results: `{recipient} to=assistant` on analysis | |
| - Parallel calls: `<|end|>` between calls, `<|call|>` on the last | |
| - `function.arguments` must be a dict (parse JSON strings before apply) | |
| -#} | |
| {%- macro ts_type_name(type_val) -%} | |
| {%- if type_val == "integer" -%} | |
| {{- "number" -}} | |
| {%- elif type_val in ["string", "number", "boolean"] -%} | |
| {{- type_val -}} | |
| {%- else -%} | |
| {{- "any" -}} | |
| {%- endif -%} | |
| {%- endmacro -%} | |
| {#- Property defaults render enum strings bare. Generic `oneOf` variants pass | |
| false because the reference renderer quotes them in that path. #} | |
| {%- macro render_default(schema, bare_enum_string=true) -%} | |
| {%- set is_enum = schema.get("enum") is sequence and schema.get("enum") is not string and schema.get("enum") -%} | |
| {%- if schema.default is string and (not is_enum or not bare_enum_string) -%} | |
| {{- '"' + schema.default + '"' -}} | |
| {%- elif schema.default is string -%} | |
| {{- schema.default -}} | |
| {%- else -%} | |
| {{- schema.default | tojson(separators=(",", ":")) -}} | |
| {%- endif -%} | |
| {%- endmacro -%} | |
| {#- The reference renderer checks the complete rendered text for `null`, so | |
| this intentionally does the same rather than inspecting schema members. #} | |
| {%- macro render_nullable_type(schema, indent) -%} | |
| {%- set rendered = json_schema_to_typescript(schema, indent) -%} | |
| {{- rendered -}} | |
| {%- if schema is mapping and schema.get("nullable") and "null" not in rendered -%} | |
| {{- " | null" -}} | |
| {%- endif -%} | |
| {%- endmacro -%} | |
| {#- Render a JSON-schema node as a TypeScript type. | |
| `indent` is the leading spaces for properties inside an object; nested | |
| objects bump it by 4. Use `schema["items"]` (not `schema.items`), since | |
| attribute access would hit dict.items. #} | |
| {%- macro json_schema_to_typescript(schema, indent=0) -%} | |
| {%- if schema is mapping and schema.get("oneOf") is sequence and schema.get("oneOf") is not string -%} | |
| {%- for variant in schema.oneOf -%} | |
| {{- "\n" + (" " * indent) + " | " -}} | |
| {{- render_nullable_type(variant, indent + 3) -}} | |
| {%- if variant is mapping and variant.get("description") is string -%} | |
| {{- " // " + variant.description -}} | |
| {%- if "default" in variant -%} | |
| {{- " default: " + render_default(variant, false) -}} | |
| {%- endif -%} | |
| {%- elif variant is mapping and "default" in variant -%} | |
| {{- " // default: " + render_default(variant, false) -}} | |
| {%- endif -%} | |
| {%- endfor -%} | |
| {#- Only a string enum becomes a literal union. An integer or untyped enum | |
| falls through to its plain type, or to `any` when it has none. #} | |
| {%- elif schema is mapping and schema.get("enum") is sequence and schema.get("enum") is not string and schema.get("enum") and schema.get("type") == "string" -%} | |
| {%- set rendered_enum = namespace(any=false) -%} | |
| {%- for val in schema.enum -%} | |
| {%- if val is string -%} | |
| {%- if rendered_enum.any %} | {% endif -%} | |
| {{- val | tojson -}} | |
| {%- set rendered_enum.any = true -%} | |
| {%- endif -%} | |
| {%- endfor -%} | |
| {%- if not rendered_enum.any %}string{%- endif -%} | |
| {%- elif schema is mapping and schema.get("type") == "object" -%} | |
| {%- if schema.get("description") is string -%} | |
| {{- (" " * indent) + "// " + schema.description + "\n" -}} | |
| {%- endif -%} | |
| {{- "{\n" -}} | |
| {%- set required = schema.required if schema.get("required") else [] -%} | |
| {%- if schema.get("properties") -%} | |
| {#- Properties render in insertion order, so a schema controls the | |
| order its parameters appear in. #} | |
| {%- for prop_name, prop_spec in schema.properties.items() -%} | |
| {%- if prop_spec is mapping and prop_spec.get("title") is string -%} | |
| {{- (" " * indent) + "// " + prop_spec.title + "\n" -}} | |
| {{- (" " * indent) + "//\n" -}} | |
| {%- endif -%} | |
| {%- if prop_spec is mapping and "oneOf" not in prop_spec and prop_spec.get("description") is string -%} | |
| {{- (" " * indent) + "// " + prop_spec.description + "\n" -}} | |
| {%- endif -%} | |
| {%- if prop_spec is mapping and prop_spec.get("examples") is sequence and prop_spec.get("examples") is not string and prop_spec.get("examples") -%} | |
| {{- (" " * indent) + "// Examples:\n" -}} | |
| {%- for example in prop_spec.examples -%} | |
| {%- if example is string -%} | |
| {{- (" " * indent) + '// - "' + example + '"\n' -}} | |
| {%- endif -%} | |
| {%- endfor -%} | |
| {%- endif -%} | |
| {%- if prop_spec is mapping and prop_spec.get("oneOf") is sequence and prop_spec.get("oneOf") is not string -%} | |
| {%- set property_description = prop_spec.get("description") if prop_spec.get("description") is string else none -%} | |
| {%- set first_variant = prop_spec.oneOf[0] if prop_spec.oneOf else {} -%} | |
| {%- set first_description = first_variant.get("description") if first_variant is mapping and first_variant.get("description") is string else none -%} | |
| {%- set render_property_description = property_description and property_description != first_description -%} | |
| {%- if render_property_description -%} | |
| {{- (" " * indent) + "// " + property_description + "\n" -}} | |
| {%- endif -%} | |
| {%- if "default" in prop_spec -%} | |
| {{- (" " * indent) + "// default: " + render_default(prop_spec) + "\n" -}} | |
| {%- endif -%} | |
| {{- (" " * indent) + prop_name -}} | |
| {%- if prop_name not in required -%}?{%- endif -%} | |
| {{- ":\n" -}} | |
| {%- for variant in prop_spec.oneOf -%} | |
| {{- (" " * indent) + " | " -}} | |
| {{- render_nullable_type(variant, indent + 3) -}} | |
| {%- set render_variant_description = variant is mapping and variant.get("description") is string and variant.description != property_description and not (loop.first and render_property_description) -%} | |
| {%- if render_variant_description -%} | |
| {{- " // " + variant.description -}} | |
| {%- if "default" in variant -%} | |
| {{- " default: " + render_default(variant) -}} | |
| {%- endif -%} | |
| {%- elif variant is mapping and "default" in variant -%} | |
| {{- " // default: " + render_default(variant) -}} | |
| {%- endif -%} | |
| {{- "\n" -}} | |
| {%- endfor -%} | |
| {{- (" " * indent) + ",\n" -}} | |
| {%- else -%} | |
| {{- (" " * indent) + prop_name -}} | |
| {%- if prop_name not in required -%}?{%- endif -%} | |
| {{- ": " -}} | |
| {{- render_nullable_type(prop_spec, indent + 4) -}} | |
| {{- "," -}} | |
| {%- if prop_spec is mapping and "default" in prop_spec -%} | |
| {{- " // default: " + render_default(prop_spec) -}} | |
| {%- endif -%} | |
| {{- "\n" -}} | |
| {%- endif -%} | |
| {%- endfor -%} | |
| {%- endif -%} | |
| {{- (" " * indent) + "}" -}} | |
| {%- elif schema is mapping and schema.get("type") == "array" -%} | |
| {%- if schema.get("items") -%} | |
| {{- json_schema_to_typescript(schema["items"], indent) + "[]" -}} | |
| {%- else -%} | |
| {{- "Array<any>" -}} | |
| {%- endif -%} | |
| {%- elif schema is mapping and schema.get("type") is string -%} | |
| {{- ts_type_name(schema.type) -}} | |
| {%- elif schema is mapping and schema.get("type") is sequence -%} | |
| {%- set rendered_types = namespace(any=false) -%} | |
| {%- for ty in schema.type -%} | |
| {%- if ty is string -%} | |
| {%- if rendered_types.any %} | {% endif -%} | |
| {%- if ty == "integer" %}number{%- else %}{{ ty }}{%- endif -%} | |
| {%- set rendered_types.any = true -%} | |
| {%- endif -%} | |
| {%- endfor -%} | |
| {%- if not rendered_types.any %}any{%- endif -%} | |
| {%- else -%} | |
| {{- "any" -}} | |
| {%- endif -%} | |
| {%- endmacro -%} | |
| {#- String-schema tools declare as `## name`; every other tool declares as | |
| `type name = (_: {...}) => any;`. Call sites emit XML either way. #} | |
| {%- macro render_tool_declaration(tool) -%} | |
| {%- set tool = tool.function if tool.function is defined else tool -%} | |
| {%- if tool.parameters is mapping and tool.parameters.get("type") == "string" and (not tool.parameters.get("properties")) -%} | |
| {{- "## " + tool.name + "\n\n" -}} | |
| {{- tool.description or "" -}} | |
| {%- else -%} | |
| {%- if tool.description -%} | |
| {%- for line in tool.description.split("\n") -%} | |
| {{- "// " + line + "\n" -}} | |
| {%- endfor -%} | |
| {%- endif -%} | |
| {#- A tool with no parameters keeps the `(_: ...)` form and renders | |
| `(_: any)`. #} | |
| {{- "type " + tool.name + " = (_: " + json_schema_to_typescript(tool.parameters) + ") => any;" -}} | |
| {%- endif -%} | |
| {%- endmacro -%} | |
| {%- macro render_tools_section(tools) -%} | |
| {{- "# Tools\n\n" -}} | |
| {%- for tool in tools -%} | |
| {{- render_tool_declaration(tool) -}} | |
| {%- if not loop.last -%} | |
| {{- "\n\n" -}} | |
| {%- endif -%} | |
| {%- endfor -%} | |
| {%- endmacro -%} | |
| {#- String values go out as-is; non-strings are JSON-encoded in their given | |
| order, at every depth. Jinja's `tojson` sorts keys by default, so mappings | |
| and sequences recurse through this macro instead of being handed to it. #} | |
| {%- macro render_json_value(value) -%} | |
| {%- if value is mapping -%} | |
| {{- "{" -}} | |
| {%- for k, v in value.items() -%} | |
| {{- k | tojson }}: {{ render_json_value(v) -}} | |
| {%- if not loop.last %}, {% endif -%} | |
| {%- endfor -%} | |
| {{- "}" -}} | |
| {%- elif value is sequence and value is not string -%} | |
| {{- "[" -}} | |
| {%- for item in value -%} | |
| {{- render_json_value(item) -}} | |
| {%- if not loop.last %}, {% endif -%} | |
| {%- endfor -%} | |
| {{- "]" -}} | |
| {%- else -%} | |
| {{- value | tojson -}} | |
| {%- endif -%} | |
| {%- endmacro -%} | |
| {%- macro render_parameter_value(value) -%} | |
| {%- if value is string -%} | |
| {{- value -}} | |
| {%- else -%} | |
| {{- render_json_value(value) -}} | |
| {%- endif -%} | |
| {%- endmacro -%} | |
| {%- macro render_tool_call_body(tool_call) -%} | |
| {%- set fn = tool_call.function if tool_call.function is defined else tool_call -%} | |
| {%- set args = fn.arguments if fn.arguments is defined else tool_call.arguments -%} | |
| {%- if args is mapping -%} | |
| {%- for param_name in args -%} | |
| {%- set value_text = render_parameter_value(args[param_name]) -%} | |
| {%- if "</rfl:parameter>" in value_text -%} | |
| {{- raise_exception("Tool-call parameter '" + (param_name | string) + "' contains the reserved delimiter </rfl:parameter>") }} | |
| {%- endif -%} | |
| {%- if not loop.first %}{{- "\n" }}{%- endif -%} | |
| {{- '<rfl:parameter name="' + param_name + '">\n' -}} | |
| {{- value_text -}} | |
| {{- '\n</rfl:parameter>' -}} | |
| {%- endfor -%} | |
| {%- elif args is defined and args is not none -%} | |
| {{- raise_exception("function.arguments must be a dict; parse JSON strings before apply_chat_template") }} | |
| {%- endif -%} | |
| {%- endmacro -%} | |
| {%- macro build_system_message(builtin_tool_definitions) -%} | |
| {%- if model_identity is not defined %} | |
| {%- set model_identity = "You are Beam, an AI assistant trained by Reflection." %} | |
| {%- endif %} | |
| {{- model_identity -}} | |
| {#- Served default: effort 512 (`max`) when the kwarg is unset or null. #} | |
| {%- if reasoning_effort is not defined or reasoning_effort is none %} | |
| {%- set reasoning_effort = 512 %} | |
| {%- endif %} | |
| {%- if reasoning_effort is string %} | |
| {%- if reasoning_effort == "minimal" %} | |
| {%- set reasoning_effort = 8 %} | |
| {%- elif reasoning_effort == "low" %} | |
| {%- set reasoning_effort = 32 %} | |
| {%- elif reasoning_effort == "medium" %} | |
| {%- set reasoning_effort = 64 %} | |
| {%- elif reasoning_effort == "high" %} | |
| {%- set reasoning_effort = 128 %} | |
| {%- elif reasoning_effort == "xhigh" %} | |
| {%- set reasoning_effort = 256 %} | |
| {%- elif reasoning_effort == "max" %} | |
| {%- set reasoning_effort = 512 %} | |
| {%- else %} | |
| {{- raise_exception("reasoning_effort must be an int or one of: minimal, low, medium, high, xhigh, max") }} | |
| {%- endif %} | |
| {%- endif %} | |
| {{- "\n\nReasoning effort: " + (reasoning_effort | string) }} | |
| {%- if builtin_tool_definitions -%} | |
| {{- "\n\n" -}} | |
| {{- render_tools_section(builtin_tool_definitions) -}} | |
| {%- endif -%} | |
| {{- "\n\n# Valid channels: analysis, final. Channel must be included for every message." -}} | |
| {%- endmacro -%} | |
| {%- if not messages %}{{ raise_exception("messages cannot be empty") }}{%- endif %} | |
| {#- `tools=` remains the serving engine's complete tool registry. The names in | |
| `builtin_tools` only select which definitions move into the system block. #} | |
| {%- set builtin_tool_names = builtin_tools | default([], true) -%} | |
| {#- Training declares `bash` in the system block with this definition, so a | |
| `bash` whose only parameter is an unconstrained `command` string is declared | |
| with it. Only descriptions and `additionalProperties` may differ, since they | |
| leave the accepted calls the same; any schema key that constrains the call | |
| keeps the supplied definition in the developer block. An omitted `required` | |
| is declared as required: every call the trained declaration invites is one | |
| the supplied schema accepts. #} | |
| {%- set trained_bash_tool = { | |
| "name": "bash", | |
| "description": "Execute a bash command. The output contains the command's stdout and stderr.\nCommands time out after 10 minutes. You can set a shorter timeout within the bash command.", | |
| "parameters": { | |
| "type": "object", | |
| "properties": {"command": {"type": "string", "description": "The command to execute"}}, | |
| "required": ["command"], | |
| }, | |
| } -%} | |
| {%- set tool_groups = namespace(builtin=[], regular=[]) -%} | |
| {%- for tool in tools | default([], true) -%} | |
| {%- set tool_definition = tool.function if tool.function is defined else tool -%} | |
| {%- set tool_name = tool_definition.name -%} | |
| {%- set tool_parameters = tool_definition.parameters if tool_definition.parameters is mapping else {} -%} | |
| {%- set tool_properties = tool_parameters.properties if tool_parameters.properties is mapping else {} -%} | |
| {%- set bash_shape = namespace(plain=true) -%} | |
| {%- for key in tool_parameters -%} | |
| {%- if key not in ["type", "properties", "required", "additionalProperties", "description"] -%} | |
| {%- set bash_shape.plain = false -%} | |
| {%- endif -%} | |
| {%- endfor -%} | |
| {%- set command_schema = tool_properties.command if tool_properties.command is mapping else {} -%} | |
| {%- for key in command_schema -%} | |
| {%- if key not in ["type", "description"] -%} | |
| {%- set bash_shape.plain = false -%} | |
| {%- endif -%} | |
| {%- endfor -%} | |
| {%- set is_trained_bash = tool_name == "bash" | |
| and bash_shape.plain | |
| and tool_parameters.get("type") == "object" | |
| and (tool_properties | list) == ["command"] | |
| and command_schema.get("type") == "string" | |
| and tool_parameters.get("required", ["command"]) == ["command"] -%} | |
| {%- if is_trained_bash -%} | |
| {%- set tool_groups.builtin = tool_groups.builtin + [trained_bash_tool] -%} | |
| {%- elif tool_name in builtin_tool_names -%} | |
| {%- set tool_groups.builtin = tool_groups.builtin + [tool] -%} | |
| {%- else -%} | |
| {%- set tool_groups.regular = tool_groups.regular + [tool] -%} | |
| {%- endif -%} | |
| {%- endfor -%} | |
| {{- "<|start|>system<|message|>" }} | |
| {{- build_system_message(tool_groups.builtin) }} | |
| {{- "<|end|>" }} | |
| {#- The leading run of system/developer messages joins, a blank line apart, | |
| into the one `# Instructions` block. Empty content counts as absent, so a | |
| run with no content emits no `# Instructions`. #} | |
| {%- set leading_instructions = namespace(count=0, parts=[]) %} | |
| {%- for message in messages %} | |
| {%- if loop.index0 == leading_instructions.count and message.role in ["system", "developer"] %} | |
| {%- set leading_instructions.count = leading_instructions.count + 1 %} | |
| {%- if message.content %} | |
| {%- set leading_instructions.parts = leading_instructions.parts + [message.content] %} | |
| {%- endif %} | |
| {%- endif %} | |
| {%- endfor %} | |
| {%- set developer_message = leading_instructions.parts | join("\n\n") %} | |
| {%- set loop_messages = messages[leading_instructions.count:] %} | |
| {%- if add_generation_prompt and allow_missing_thinking is defined and allow_missing_thinking == false %} | |
| {%- for message in loop_messages %} | |
| {%- if message.role == 'assistant' and not (message.thinking or message.reasoning_content or message.reasoning) %} | |
| {{- raise_exception("The assistant turn at messages[" + ((loop.index0 + leading_instructions.count) | string) + "] 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.") }} | |
| {%- endif %} | |
| {%- endfor %} | |
| {%- endif %} | |
| {%- set has_tools = tool_groups.regular %} | |
| {%- if developer_message or has_tools %} | |
| {{- "<|start|>developer<|message|>" }} | |
| {%- if developer_message %} | |
| {{- "# Instructions\n\n" }} | |
| {{- developer_message }} | |
| {%- if has_tools %} | |
| {{- "\n\n" }} | |
| {%- endif %} | |
| {%- endif %} | |
| {%- if has_tools %} | |
| {{- render_tools_section(tool_groups.regular) }} | |
| {%- endif %} | |
| {{- "<|end|>" }} | |
| {%- endif %} | |
| {%- set tool_call_registry = namespace(entries=[]) %} | |
| {%- for message in loop_messages -%} | |
| {%- if message.role == 'assistant' -%} | |
| {%- if message.content %} | |
| {%- if "<|channel|>analysis<|message|>" in message.content or "<|channel|>final<|message|>" in message.content %} | |
| {{- raise_exception("Pass plain text in API fields, not Harmony wire tags. Use 'thinking' for analysis and 'content' for final.") }} | |
| {%- endif %} | |
| {%- endif %} | |
| {%- set thinking_text = message.thinking or message.reasoning_content or message.reasoning %} | |
| {%- if thinking_text %} | |
| {%- if "<|channel|>analysis<|message|>" in thinking_text or "<|channel|>final<|message|>" in thinking_text %} | |
| {{- raise_exception("Pass plain text in 'thinking'/'reasoning_content'/'reasoning' without Harmony wire tags.") }} | |
| {%- endif %} | |
| {%- endif %} | |
| {{- "<|start|>assistant" }} | |
| {%- generation %} | |
| {{- "<|channel|>analysis<|message|>" + (thinking_text or "") + "<|end|>" }} | |
| {%- if message.tool_calls %} | |
| {%- for tool_call in message.tool_calls %} | |
| {%- set fn = tool_call.function if tool_call.function is defined else tool_call %} | |
| {%- set tool_name = fn.name if fn.name is defined else tool_call.name %} | |
| {%- set call_id = tool_call.id if tool_call.id is defined else (fn.id if fn.id is defined else none) %} | |
| {%- if call_id and tool_name %} | |
| {%- set tool_call_registry.entries = tool_call_registry.entries + [{"id": call_id, "name": tool_name}] %} | |
| {%- endif %} | |
| {{- "<|start|>assistant to=" + tool_name + "<|channel|>analysis <|constrain|>xml<|message|>" }} | |
| {{- render_tool_call_body(tool_call) }} | |
| {%- if loop.last %} | |
| {{- "<|call|>" }} | |
| {%- else %} | |
| {{- "<|end|>" }} | |
| {%- endif %} | |
| {%- endfor %} | |
| {%- else %} | |
| {{- "<|start|>assistant<|channel|>final<|message|>" + (message.content or "") + "<|return|>" }} | |
| {%- endif %} | |
| {%- endgeneration %} | |
| {%- elif message.role == 'tool' -%} | |
| {%- set resolved_tool = namespace(name=none) %} | |
| {%- if message.name is defined and message.name %} | |
| {%- set resolved_tool.name = message.name %} | |
| {%- elif message.tool_call_id is defined %} | |
| {%- for entry in tool_call_registry.entries %} | |
| {%- if entry.id == message.tool_call_id %} | |
| {%- set resolved_tool.name = entry.name %} | |
| {%- endif %} | |
| {%- endfor %} | |
| {%- endif %} | |
| {%- if resolved_tool.name is none %} | |
| {{- raise_exception("Tool message needs 'name' or a matching prior tool_call 'id' / 'tool_call_id'.") }} | |
| {%- endif %} | |
| {{- "<|start|>" + resolved_tool.name + " to=assistant<|channel|>analysis<|message|>" + (message.content or "") + "<|end|>" }} | |
| {%- elif message.role == 'user' -%} | |
| {{- "<|start|>user<|message|>" + (message.content or "") + "<|end|>" }} | |
| {%- endif -%} | |
| {%- endfor -%} | |
| {%- if add_generation_prompt -%} | |
| <|start|>assistant | |
| {%- endif -%} | |