Skip to content

Add support for GPT-OSS Harmony-style chat templates - #24

Open
AndrewFasano wants to merge 1 commit into
simonw:mainfrom
AndrewFasano:feat/gpt_oss_thinking
Open

Add support for GPT-OSS Harmony-style chat templates#24
AndrewFasano wants to merge 1 commit into
simonw:mainfrom
AndrewFasano:feat/gpt_oss_thinking

Conversation

@AndrewFasano

Copy link
Copy Markdown

Harmony-based models (such as GPT-OSS variants) use a structured two-channel chat format: an internal analysis segment and the user-visible final answer, each wrapped in <|channel|> tags. When the Hugging Face Harmony chat template processes history, it requires:

  • the final portion in content
  • the analysis portion in a separate thinking field
  • no raw <|channel|> tags inside either field

Before this PR, llm-mlx stored the entire raw model output (including both channels and tags) as the assistant message. On subsequent turns, when the history was passed back to apply_chat_template(), GPT-OSS models would raise:

jinja2.exceptions.TemplateError: You have passed a message containing <|channel|> tags in the content field...

This PR detects Harmony chat templates and:

  • parses the model’s response into analysis + final segments
  • replays only the final text in content
  • stores analysis in thinking (not shown to the user)
  • removes all <|channel|> markers from history
  • leaves non-Harmony models and streaming behavior unchanged

This enables correct multi-turn chat with GPT-OSS / Harmony models using llm chat.

A similar upstream change was proposed in mlx-lm (see ml-explore/mlx-lm#365), but maintainers stated that the application layer is responsible for providing well-formed chat messages — so I think that means this behavior should be implemented here instead.

@AndrewFasano
AndrewFasano force-pushed the feat/gpt_oss_thinking branch from bd31b7b to a337fa1 Compare December 5, 2025 07:22
Harmony models use a two-channel response format where the model generates
both analysis (thinking) and final answer sections using <|channel|> tags.
This enables the model to show its reasoning process alongside the answer.

When reconstructing multi-turn conversations, we now:
- Detect Harmony templates by checking for <|channel|> and 'thinking' markers
- Parse saved responses to extract analysis and final sections separately
- Build chat messages with a 'thinking' field containing the analysis
- This ensures the tokenizer's chat template receives properly structured data

The implementation adds two helper functions:
- _tokenizer_uses_harmony(): Detects Harmony-style chat templates
- _split_harmony_message(): Parses responses into analysis and final sections

During streaming, the full response (including all tags) is displayed to the
user and saved automatically by the llm framework. The parsing only happens
when reconstructing conversation history for multi-turn interactions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@AndrewFasano
AndrewFasano force-pushed the feat/gpt_oss_thinking branch from a337fa1 to da813b0 Compare December 5, 2025 07:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant