首页 / 资料库 / OpenAI 实践手册

资料库18 分钟读完MITOpenAIgpt-oss响应格式

OpenAI harmony 响应格式

译自《OpenAI harmony response format》 · 查看英文原文

原文出处OpenAI harmony response format 原作者:OpenAI · 许可证:MIT License 中文译本由诸葛AI学院整理,仅供学习参考,版权归原作者与 OpenAI 所有。

(译者注:文中 harmony 格式的代码块是发给模型的提示词原文,请保持英文原样使用;Python 代码块里的注释已译成中文。)

OpenAI harmony 响应格式

gpt-oss 系列模型是用 harmony 响应格式(response format)训练出来的。这个格式负责三件事:定义对话结构、生成推理(reasoning)输出、组织函数调用(function calling)。如果你不直接跑 gpt-oss,而是通过 API 或者 Ollama 这类服务商来用,就不用操心这个格式,推理方案会自动帮你处理好。如果你要自建推理方案,这篇指南会带你走一遍提示词格式。该格式在设计上模仿了 OpenAI Responses API,用过那个 API 的人应该会觉得眼熟。注意:gpt-oss 必须配合 harmony 格式使用,否则无法正常工作。

核心概念

角色

模型处理的每条消息都带有一个角色(role)。模型认识五种角色:

角色 用途
system 系统消息用来指定推理强度(reasoning effort),以及知识截止日期、内置工具等元信息
developer 开发者消息用来提供模型指令(也就是通常所说的"系统提示词"),以及可用的函数工具
user 通常代表模型的输入
assistant 模型的输出,可以是一次工具调用,也可以是一条消息。输出还可能关联某个"通道",用来说明这条消息的意图
tool 代表工具调用的输出。具体的工具名会作为消息里的角色名使用

当指令发生冲突时,这五种角色也代表模型遵循的信息层级:system > developer > user > assistant > tool

通道

assistant 消息可以输出到三个不同的"通道"(channel),用来区分给用户看的内容和内部消息:

通道 用途
final 标记为 final 通道的消息是准备展示给终端用户的,代表模型的正式回答
analysis 模型用这个通道输出思维链(chain of thought,CoT)消息。重要: analysis 通道的消息没有经过和 final 消息同等标准的安全训练,不要展示给终端用户
commentary 函数工具调用通常触发在 commentary 通道,内置工具则通常触发在 analysis 通道,但内置工具偶尔也会输出到 commentary。模型偶尔还会用这个通道在连续调用多个函数前生成一段前导说明(preamble,见下文)

harmony 渲染库

我们建议尽可能使用官方 harmony 渲染库,PyPI 和 crates.io 上都有(PyPI: openai-harmonycrates.io: openai-harmony)。它会自动把消息渲染成正确格式,并转成可供模型处理的令牌。

下面是一个用渲染库构造系统提示词和一段简短对话的例子:

```py from openai_harmony import ( Author, Conversation, DeveloperContent, HarmonyEncodingName, Message, Role, SystemContent, ToolDescription, load_harmony_encoding, ReasoningEffort )

encoding = load_harmony_encoding(HarmonyEncodingName.HARMONY_GPT_OSS)

system_message = ( SystemContent.new() .with_reasoning_effort(ReasoningEffort.HIGH) .with_conversation_start_date("2025-06-28") )

developer_message = ( DeveloperContent.new() .with_instructions("Always respond in riddles") .with_function_tools( [ ToolDescription.new( "get_current_weather", "Gets the current weather in the provided location.", parameters={ "type": "object", "properties": { "location": { "type": "string", "description": "The city and state, e.g. San Francisco, CA", }, "format": { "type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius", }, }, "required": ["location"], }, ), ] ) )

convo = Conversation.from_messages( [ Message.from_role_and_content(Role.SYSTEM, system_message), Message.from_role_and_content(Role.DEVELOPER, developer_message), Message.from_role_and_content(Role.USER, "What is the weather in Tokyo?"), Message.from_role_and_content( Role.ASSISTANT, 'User asks: "What is the weather in Tokyo?" We need to use get_current_weather tool.', ).with_channel("analysis"), Message.from_role_and_content(Role.ASSISTANT, '{"location": "Tokyo"}') .with_channel("commentary") .with_recipient("functions.get_current_weather") .with_content_type("<|constrain|> json"), Message.from_author_and_content( Author.new(Role.TOOL, "functions.get_current_weather"), '{ "temperature": 20, "sunny": true }', ).with_channel("commentary"), ] )

tokens = encoding.render_conversation_for_completion(convo, Role.ASSISTANT)

收到令牌响应之后

不要把停止符(stop token)传进去

parsed_response = encoding.parse_messages_from_completion_tokens(new_tokens, Role.ASSISTANT) ```

另外,openai_harmony 库还提供了一个 StreamableParser,可以在模型生成新令牌的过程中边解析边解码。比如你要流式输出内容,或者要在解码过程中处理 Unicode 字符,它就派上用场了。

```py from openai_harmony import ( load_harmony_encoding, Role, StreamableParser, HarmonyEncodingName )

encoding = load_harmony_encoding(HarmonyEncodingName.HARMONY_GPT_OSS) stream = StreamableParser(encoding, role=Role.ASSISTANT)

tokens = [ 200005,35644,200008,1844,31064,25,392,4827,382,220,17,659,220,17,16842,12295,81645, 13,51441,6052,13,200007,200006,173781,200005,17196,200008,17,659,220,17,314,220,19, 13,200002 ]

for token in tokens: stream.process(token) print("--------------------------------") print("current_role", stream.current_role) print("current_channel", stream.current_channel) print("last_content_delta", stream.last_content_delta) print("current_content_type", stream.current_content_type) print("current_recipient", stream.current_recipient) print("current_content", stream.current_content) ```

提示词格式

如果你选择自己实现渲染器,就必须遵守以下格式。

特殊令牌

模型用一组特殊令牌(special tokens)来识别输入的结构。如果你用 tiktoken,这些令牌编码在 o200k_harmony 编码里。所有特殊令牌都是 <|类型|> 这种写法。

特殊令牌 用途 令牌 ID
<|start|> 标记一条消息的开始,后面跟着消息"头部"信息,头部以角色开头 200006
<|end|> 标记一条消息的结束 200007
<|message|> 标记从消息"头部"过渡到实际内容 200008
<|channel|> 标记过渡到头部的通道信息 200005
<|constrain|> 标记过渡到工具调用里的数据类型定义 200003
<|return|> 表示模型已采样完这条响应消息。这是一个合法的"停止符"(stop token),收到它就该停止推理 200002
<|call|> 表示模型想要调用一个工具。这是一个合法的"停止符",收到它就该停止推理 200012

消息格式

harmony 响应格式由一条条"消息"组成,模型可能一口气生成多条消息。消息的通用结构如下:

<|start|>{header}<|message|>{content}<|end|>

{header}(头部)里包含一串元信息,其中有角色。<|end|> 表示一条完整消息的结束,但模型也可能用到其他停止符:<|call|> 用于发起工具调用,<|return|> 表示模型已完成生成。

对话格式

按上面的消息格式,最基础的对话格式就是一条 user 消息,加上一条 assistant 消息的开头。

输入示例

<|start|>user<|message|>What is 2 + 2?<|end|> <|start|>assistant

输出会以 channel 开头,例如 analysis,用来输出思维链。模型可能输出多条消息(主要是思维链消息),多条之间用 <|end|> 令牌分隔。

生成结束后,模型会停在两个令牌之一:<|return|> 表示最终答案已生成完毕,<|call|> 表示需要执行一次工具调用。无论哪种,都意味着你应该停止推理。

输出示例

<|channel|>analysis<|message|>User asks: "What is 2 + 2?" Simple arithmetic. Provide answer.<|end|> <|start|>assistant<|channel|>final<|message|>2 + 2 = 4.<|return|>

final 通道里放的就是给用户请求的答案。关于思维链的更多细节,见下文"推理"一节。

实现要点: <|return|> 只在解码时起停止符作用。把 assistant 生成的回复加入下一轮对话历史时,要把结尾的 <|return|> 换成 <|end|>,这样存下来的每条消息都是完整的 <|start|>{header}<|message|>{content}<|end|> 结构。也就是说,提示词里的历史消息都应以 <|end|> 收尾。用于监督目标或训练样本时,以 <|return|> 结尾是合适的;持久化的对话历史则要统一成 <|end|>

系统消息格式

系统消息用来向系统提供一般性信息,这和其他提示词格式里所说的"系统提示词"不是一回事,那个对应的是开发者消息格式。我们用系统消息来定义以下内容:

  1. 模型的身份(identity):固定写 You are ChatGPT, a large language model trained by OpenAI.,不要改动。想改变模型的身份,请在开发者消息里写指令。
  2. 元信息日期:具体指 Knowledge cutoff:Current date: 这两项。
  3. 推理强度:分 highmediumlow 三档。
  4. 可用通道:为了获得最佳表现,这里应该写成 analysiscommentaryfinal 三个。
  5. 内置工具:模型训练时接触过 pythonbrowser 两种工具,细节见下文"内置工具"一节。

如果你定义了函数,系统消息里还应加一句说明:所有函数工具调用都必须走 commentary 通道。

为了获得最佳表现,请尽量严格地照这个格式来。

系统消息示例

你应该使用的最基础的系统消息如下:

``` <|start|>system<|message|>You are ChatGPT, a large language model trained by OpenAI. Knowledge cutoff: 2024-06 Current date: 2025-06-28

Reasoning: high

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

```

如果开发者消息里出现了函数调用,就改用这个版本:

``` <|start|>system<|message|>You are ChatGPT, a large language model trained by OpenAI. Knowledge cutoff: 2024-06 Current date: 2025-06-28

Reasoning: high

Valid channels: analysis, commentary, final. Channel must be included for every message.

Calls to these tools must go to the commentary channel: 'functions'.<|end|> ```

开发者消息格式

开发者消息就是我们通常说的"系统提示词"。它包含提供给模型的指令,还可以选填一份供模型使用的函数工具清单,或者结构化输出里要求模型遵守的输出格式。

如果你不用函数工具调用,开发者消息长这样:

``` <|start|>developer<|message|># Instructions

{instructions}<|end|> ```

{instructions} 替换成你的"系统提示词"即可。

想定义函数调用工具,见下文"函数调用"一节;想为结构化输出定义输出格式,见下文"结构化输出"一节。

推理

gpt-oss 是推理模型。默认情况下,模型做中等强度的推理。想控制推理,可以在系统消息里把推理等级写成 lowmediumhigh。推荐写法:

Reasoning: high

模型会把原始思维链(CoT)作为 assistant 消息输出到 analysis 通道,最终回答则输出到 final 通道。

比如对 What is 2 + 2? 这个问题,模型输出可能长这样:

<|channel|>analysis<|message|>User asks: "What is 2 + 2?" Simple arithmetic. Provide answer.<|end|> <|start|>assistant<|channel|>final<|message|>2 + 2 = 4.<|return|>

这个例子里,思维链是:

User asks: "What is 2 + 2?" Simple arithmetic. Provide answer.

实际答案是:

2 + 2 = 4

重要: 思维链部分的安全训练标准和最终输出不一样。不要把思维链展示给用户,它里面可能含有害内容。更多信息见模型卡

后续采样时怎么处理推理输出

一般来说,只要 assistant 之前的回复以 final 通道的消息收尾,后续采样时就应该丢掉所有旧的思维链内容。也就是说,如果第一次输入是这个:

<|start|>user<|message|>What is 2 + 2?<|end|> <|start|>assistant

得到了这个输出:

<|channel|>analysis<|message|>User asks: "What is 2 + 2?" Simple arithmetic. Provide answer.<|end|> <|start|>assistant<|channel|>final<|message|>2 + 2 = 4.<|return|>

那下一轮采样要正常工作,输入应该是:

<|start|>user<|message|>What is 2 + 2?<|end|> <|start|>assistant<|channel|>final<|message|>2 + 2 = 4.<|end|> <|start|>user<|message|>What about 9 / 2?<|end|> <|start|>assistant

例外情况是工具调用/函数调用。模型可以在思维链里调用工具,所以后续采样时必须把之前的思维链一并回传给模型。完整例子见下文"函数调用"一节。

函数调用

定义可用工具

所有对模型开放的函数,都应该写在开发者消息里一个专门的 Tools 小节中。

函数定义采用一种类 TypeScript 的类型语法,并把函数包进专门的 functions 命名空间。严格贴合这个格式能提高函数调用的准确率。关于怎么把参数的 JSON schema 转成这种格式,可以查看 harmony 渲染库的源码。这里先给几条通用的排版惯例:

  • 不带参数的函数,一律定义成 type {function_name} = () => any
  • 带参数的函数,参数名写成 _,类型定义内联展开
  • 字段说明写成注释,放在字段定义的上一行
  • 返回类型一律写 any
  • 每个函数定义后面留一个空行
  • 把函数包进命名空间,一般用 functions,这样不会和模型训练时接触过的其他工具冲突

下面是一个完整的输入示例,里面定义了两个函数:

``` <|start|>system<|message|>You are ChatGPT, a large language model trained by OpenAI. Knowledge cutoff: 2024-06 Current date: 2025-06-28

Reasoning: high

Valid channels: analysis, commentary, final. Channel must be included for every message.

Calls to these tools must go to the commentary channel: 'functions'.<|end|><|start|>developer<|message|># Instructions

Use a friendly tone.

Tools

functions

namespace functions {

// Gets the location of the user. type get_location = () => any;

// Gets the current weather in the provided location. type get_current_weather = (_: { // The city and state, e.g. San Francisco, CA location: string, format?: "celsius" | "fahrenheit", // default: celsius }) => any;

// Gets the current weather in the provided list of locations. type get_multiple_weathers = (_: { // List of city and state, e.g. ["San Francisco, CA", "New York, NY"] locations: string[], format?: "celsius" | "fahrenheit", // default: celsius }) => any;

} // namespace functions<|end|><|start|>user<|message|>What is the weather like in SF?<|end|><|start|>assistant ```

接收工具调用

如果模型决定调用某个工具,它会在消息头部用 to={name} 的格式声明接收方(recipient)。比如它决定触发上面那个 get_current_weather 函数,就会在头部写 to=functions.get_current_weather,并按系统消息里的约定把通道设为 commentary接收方既可以写在头部的角色部分,也可以写在通道部分。

模型还可能给出一个 <|constrain|> 令牌,说明这次工具调用的输入类型。这个例子里传入的是 JSON,所以 <|constrain|> 的值是 json

<|channel|>analysis<|message|>Need to use function get_current_weather.<|end|><|start|>assistant<|channel|>commentary to=functions.get_current_weather <|constrain|>json<|message|>{"location":"San Francisco"}<|call|>

处理工具调用

函数调用执行完之后,我们要把结果交还给模型:在调用消息后面补一条带输出的工具消息。

工具消息的格式如下:

<|start|>{toolname} to=assistant<|channel|>commentary<|message|>{output}<|end|>

套用到上面的例子里就是:

<|start|>functions.get_current_weather to=assistant<|channel|>commentary<|message|>{"sunny": true, "temperature": 20}<|end|>

收齐工具调用的输出之后,你就可以带着完整内容再跑一次推理:

``` <|start|>system<|message|>You are ChatGPT, a large language model trained by OpenAI. Knowledge cutoff: 2024-06 Current date: 2025-06-28

Reasoning: high

Valid channels: analysis, commentary, final. Channel must be included for every message.

Calls to these tools must go to the commentary channel: 'functions'.<|end|><|start|>developer<|message|># Instructions

Use a friendly tone.

Tools

functions

namespace functions {

// Gets the location of the user. type get_location = () => any;

// Gets the current weather in the provided location. type get_current_weather = (_: { // The city and state, e.g. San Francisco, CA location: string, format?: "celsius" | "fahrenheit", // default: celsius }) => any;

// Gets the current weather in the provided list of locations. type get_multiple_weathers = (_: { // List of city and state, e.g. ["San Francisco, CA", "New York, NY"] locations: string[], format?: "celsius" | "fahrenheit", // default: celsius }) => any;

} // namespace functions<|end|><|start|>user<|message|>What is the weather like in SF?<|end|><|start|>assistant<|channel|>analysis<|message|>Need to use function get_current_weather.<|end|><|start|>assistant<|channel|>commentary to=functions.get_current_weather <|constrain|>json<|message|>{"location":"San Francisco"}<|call|><|start|>functions.get_current_weather to=assistant<|channel|>commentary<|message|>{"sunny": true, "temperature": 20}<|end|><|start|>assistant ```

从上面的例子里可以看到,我们回传给模型继续采样的不只是函数结果,还有之前的思维链("Need to use function get_current_weather.")。模型需要这些信息才能接着往下想,或者直接给出最终答案。

前导说明

有时模型会先生成一段"前导说明"(preamble),告诉用户它准备调用哪些工具,比如打算连调多个工具的时候。这种情况下,它会在 commentary 通道输出一条 assistant 消息。和思维链不同,这条消息是给用户看的。

``` <|channel|>analysis<|message|>{long chain of thought}<|end|><|start|>assistant<|channel|>commentary<|message|>Action plan: 1. Generate an HTML file 2. Generate a JavaScript for the Node.js server 3. Start the server


Will start executing the plan step by step<|end|><|start|>assistant<|channel|>commentary to=functions.generate_file<|constrain|>json<|message|>{"template": "basic_html", "path": "index.html"}<|call|> ```

这个例子里,模型生成了一份行动计划,告知用户它即将执行的几个步骤。

结构化输出

想控制模型的输出行为,可以在开发者消息的末尾定义一个响应格式,结构如下:

```

Response Formats

{format name}

// {description or context} {schema}<|end|> ```

格式名称的作用,类似于你在 Responses API 里为 schema 指定的名字;schema 本身用 JSON Schema。

举个例子,下面这条开发者消息定义了一个购物清单的 schema:

``` <|start|>developer<|message|># Instructions

You are a helpful shopping assistant

Response Formats

shopping_list

{"properties":{"items":{"type":"array","description":"entries on the shopping list","items":{"type":"string"}}},"type":"object"}<|end|><|start|>user<|message|>I need to buy coffee, soda and eggs<|end|><|start|>assistant ```

不过,光靠这段提示词只能影响模型的行为,并不能保证它完全遵守 schema。要做到严格遵守,你还是得自己构造文法(grammar),在采样时强制执行 schema。

内置工具

gpt-oss 模型在训练时配了两个常用工具:一个用来查找信息,一个用来执行 Python 代码,目的都是帮模型把结果做得更好。

如果你想实现这套能力,建议照下面的格式来,可靠性和准确率会更高。

这些工具要写在系统消息里,不是开发者消息,方法是加一个 # Tools 小节。

浏览器工具

定义浏览器工具,就把下面这段加进系统消息:

``` <|start|>system<|message|>You are ChatGPT, a large language model trained by OpenAI. Knowledge cutoff: 2024-06 Current date: 2025-06-28

Reasoning: high

Tools

browser

// Tool for browsing. // The cursor appears in brackets before each browsing display: [{cursor}]. // Cite information from the tool using the following format: // 【{cursor}†L{line_start}(-L{line_end})?】, for example: 【6†L9-L11】 or 【8†L3】. // Do not quote more than 10 words directly from the tool output. // sources=web (default: web) namespace browser {

// Searches for information related to query and displays topn results. type search = (_: { query: string, topn?: number, // default: 10 source?: string, }) => any;

// Opens the link id from the page indicated by cursor starting at line number loc, showing num_lines lines. // Valid link ids are displayed with the formatting: 【{id}†.*】. // If cursor is not provided, the most recent page is implied. // If id is a string, it is treated as a fully qualified URL associated with source. // If loc is not provided, the viewport will be positioned at the beginning of the document or centered on the most relevant passage, if available. // Use this function without id to scroll to a new location of an opened page. type open = (_: { id?: number | string, // default: -1 cursor?: number, // default: -1 loc?: number, // default: -1 num_lines?: number, // default: -1 view_source?: boolean, // default: false source?: string, }) => any;

// Finds exact matches of pattern in the current page, or the page given by cursor. type find = (_: { pattern: string, cursor?: number, // default: -1 }) => any;

} // namespace browser

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

```

模型决定调用浏览器时,用的格式和函数调用一样,只是有两个重要区别:

  1. 请求发到 analysis 通道
  2. 接收方分别写成 browser.searchbrowser.openbrowser.find

Python 工具

``` <|start|>system<|message|>You are ChatGPT, a large language model trained by OpenAI. Knowledge cutoff: 2024-06 Current date: 2025-06-28

Reasoning: high

Tools

python

Use this tool to execute Python code in your chain of thought. The code will not be shown to the user. This tool should be used for internal reasoning, but not for code that is intended to be visible to the user (e.g. when creating plots, tables, or files).

When you send a message containing Python code to python, it will be executed in a stateful Jupyter notebook environment. python will respond with the output of the execution or time out after 120.0 seconds. The drive at '/mnt/data' can be used to save and persist user files. Internet access for this session is UNKNOWN. Depends on the cluster.

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

```

模型决定执行 Python 代码时,用的格式和函数调用一样,只是有两个重要区别:

  1. 请求发到 analysis 通道
  2. 接收方固定是 python

这篇在讲什么,跟咱们的课怎么对?

资料库是大厂公开教材的中文译本,偏原理和工程做法。想看面向中小企业的白话版本,去入门课场景课