gpt-oss 모델이 사용하는 Harmony 응답 형식의 개념, 렌더러 라이브러리, 프롬프트/메시지 형식, 추론 출력 처리, 함수 호출, 구조화된 출력, 내장 도구 정의 방법을 설명합니다.
URL: https://developers.openai.com/cookbook/articles/openai-harmony/
Title: OpenAI Harmony Response Format
gpt-oss 모델은 대화 구조를 정의하고, 추론 출력을 생성하며, 함수 호출을 구조화하기 위한 Harmony 응답 형식으로 학습되었습니다. gpt-oss를 직접 사용하지 않고 API나 Ollama 같은 제공자를 통해 사용한다면, 추론 솔루션이 포맷팅을 처리하므로 이 부분을 신경 쓸 필요가 없습니다. 하지만 자체 추론 솔루션을 구축하는 경우, 이 가이드는 프롬프트 형식을 안내합니다. 이 형식은 OpenAI Responses API를 모방하도록 설계되었기 때문에, 이전에 해당 API를 사용해 본 적이 있다면 익숙하게 느껴질 것입니다. gpt-oss는 Harmony 형식을 사용하지 않으면 올바르게 동작하지 않으므로, Harmony 형식 없이 사용해서는 안 됩니다.
모델이 처리하는 모든 메시지에는 역할(role)이 연결되어 있습니다. 모델은 다섯 가지 역할을 알고 있습니다:
| 역할 | 목적 |
|---|---|
system | 시스템 메시지는 추론 노력(reasoning effort), 지식 컷오프 같은 메타 정보, 내장 도구를 지정하는 데 사용됩니다 |
developer | 개발자 메시지는 모델에 대한 지시(일반적으로 “시스템 프롬프트”로 여겨지는 것)와 사용 가능한 함수 도구를 제공하는 데 사용됩니다 |
user | 일반적으로 모델에 대한 입력을 나타냅니다 |
assistant | 모델이 출력하는 내용으로, 도구 호출 또는 메시지 출력이 될 수 있습니다. 출력은 또한 메시지 의도를 식별하는 특정 “채널(channel)”과 연관될 수 있습니다. |
tool | 도구 호출의 출력을 나타내는 메시지입니다. 특정 도구 이름이 메시지 내부에서 역할로 사용됩니다. |
이 역할들은 또한 지시 충돌(instruction conflicts)이 있을 때 모델이 적용하는 정보 계층을 나타냅니다: system>developer>user>assistant>tool
어시스턴트 메시지는 세 가지 서로 다른 “채널”로 출력될 수 있습니다. 채널은 사용자에게 보여줄 응답과 내부용 메시지를 분리하는 데 사용됩니다.
| 채널 | 목적 |
|---|---|
final | final 채널로 태깅된 메시지는 최종 사용자에게 보여줄 의도로 작성된 메시지이며, 모델의 응답을 나타냅니다. |
analysis | 모델의 CoT(Chain of Thought, 사고 과정)에 사용되는 메시지입니다. 중요: analysis 채널의 메시지는 final 메시지와 동일한 안전 기준을 따르지 않습니다. 최종 사용자에게 보여주지 마세요. |
commentary | 함수 도구 호출은 보통 commentary 채널에서 트리거되며, 내장 도구는 일반적으로 analysis 채널에서 트리거됩니다. 다만 때로는 내장 도구가 commentary로 출력되기도 합니다. 또한 이 채널은 모델이 여러 함수를 호출하기 전 프리앰블(preamble)을 생성하는 데 사용되기도 합니다. |
가능하다면 PyPI 또는 crates.io의 Harmony 렌더러 사용을 권장합니다. 이 라이브러리는 메시지를 올바른 형식으로 자동 렌더링하고, 모델이 처리할 수 있도록 토큰으로 변환해 줍니다.
아래는 렌더러를 사용해 시스템 프롬프트와 짧은 대화를 구성하는 예시입니다.
pythonfrom 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 토큰은 전달하지 마세요 parsed_response = encoding.parse_messages_from_completion_tokens(new_tokens, Role.ASSISTANT)
또한 openai_harmony 라이브러리에는 모델이 새 토큰을 생성하는 동안 파싱 및 디코딩을 수행하는 StreamableParser도 포함되어 있습니다. 이는 예를 들어 출력 스트리밍을 하거나 디코딩 중 유니코드 문자를 처리할 때 유용할 수 있습니다.
pythonfrom 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)
직접 렌더러를 만들기로 했다면, 아래 형식을 반드시 따라야 합니다.
모델은 입력 구조를 식별하기 위해 특수 토큰 세트를 사용합니다. tiktoken을 사용한다면, 이 토큰들은 o200k_harmony 인코딩에 포함되어 있습니다. 모든 특수 토큰은 <|type|> 형식을 따릅니다.
| 특수 토큰 | 목적 | 토큰 ID |
|---|---|---|
| < | start | > |
| < | end | > |
| < | message | > |
| < | channel | > |
| < | constrain | > |
| < | return | > |
| < | call | > |
Harmony 응답 형식은 모델이 한 번에 여러 메시지를 생성할 수 있는 “메시지”들로 구성됩니다. 메시지의 일반적인 구조는 다음과 같습니다:
<|start|>{header}<|message|>{content}<|end|>
{header}에는 역할을 포함한 일련의 메타 정보가 들어갑니다. <|end|>는 완전히 완료된 메시지의 끝을 나타내지만, 모델은 도구 호출을 위한 <|call|>, 완성을 끝냈음을 나타내는 <|return|> 같은 다른 stop 토큰을 사용할 수도 있습니다.
위 메시지 형식을 따르면, 가장 기본적인 채팅 형식은 user 메시지와 assistant 메시지의 시작으로 구성됩니다.
<|start|>user<|message|>What is 2 + 2?<|end|>
<|start|>assistant
출력은 channel을 지정하는 것으로 시작합니다. 예를 들어 CoT를 출력하려면 analysis가 됩니다. 모델은 여러 메시지(주로 CoT 메시지)를 출력할 수도 있으며, 이때 <|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 채널에는 사용자의 요청에 대한 답이 들어갑니다. CoT에 대한 자세한 내용은 reasoning 섹션을 참고하세요.
구현 참고: <|return|>은 디코딩 시점(decode-time)에서만 stop token입니다. 다음 턴을 위해 대화 히스토리에 어시스턴트의 생성된 답변을 추가할 때는, 저장된 메시지가 <|start|>{header}<|message|>{content}<|end|> 형태로 완전하게 되도록, 뒤의 <|return|>을 <|end|>로 바꾸세요. 따라서 프롬프트의 이전 메시지들은 <|end|>로 끝나야 합니다. 지도 학습 타깃/학습 예시에서는 <|return|>으로 끝내는 것이 적절하지만, 영속적으로 저장하는 히스토리에서는 <|end|>로 정규화하세요.
시스템 메시지는 시스템에 대한 일반 정보를 제공하는 데 사용됩니다. 이는 다른 프롬프트 형식에서 흔히 말하는 “시스템 프롬프트”와는 다릅니다. 그에 해당하는 것은 개발자 메시지 형식을 참고하세요.
시스템 메시지는 다음을 정의하는 데 사용합니다:
You are ChatGPT, a large language model trained by OpenAI.로 유지해야 합니다. 모델 정체성을 바꾸려면 개발자 메시지의 지시를 사용하세요.Knowledge cutoff:와 Current date:high, medium, low 수준으로 지정analysis, commentary, final에 매핑하는 것이 좋습니다.python, browser 도구 모두로 학습되었습니다. 자세한 내용은 내장 도구 섹션을 확인하세요.함수를 정의하는 경우, 모든 함수 도구 호출은 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 모델은 추론 모델입니다. 기본적으로 모델은 중간(medium) 수준의 추론을 수행합니다. 추론을 제어하려면 시스템 메시지에서 추론 수준을 low, medium, high로 지정할 수 있습니다. 권장 형식은 다음과 같습니다:
Reasoning: high
모델은 raw CoT(사고 과정)를 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|>
이 경우 CoT는
User asks: “What is 2 + 2?” Simple arithmetic. Provide answer.
실제 답변은:
2 + 2 = 4
중요:
모델은 사고 과정(chain-of-thought)에 대해 최종 출력과 동일한 안전 기준으로 학습되지 않았습니다. 사고 과정에는 유해한 콘텐츠가 포함될 수 있으므로 사용자에게 보여주지 마세요. 모델 카드에서 자세히 알아보기.
일반적으로 어시스턴트의 응답이 final 채널의 메시지로 끝났다면, 이후 샘플링에서는 이전 CoT 콘텐츠를 버려야 합니다. 예를 들어 첫 입력이 다음과 같고:
<|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 네임스페이스로 감쌉니다. 함수 호출 정확도를 높이기 위해 이 형식을 최대한 엄격히 따르는 것이 중요합니다. Harmony 렌더러 코드베이스에서 JSON Schema 인자 정의를 이 형식으로 변환하는 방식도 확인할 수 있으며, 일반적인 포맷팅 관행은 다음과 같습니다:
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가 됩니다. recipient는 헤더의 role 또는 channel 섹션에 정의될 수 있습니다.
또한 모델은 도구 호출 입력 타입을 나타내기 위해 <|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|>
함수 호출을 처리한 뒤에는, 호출 결과를 새로운 tool 메시지로 모델에 다시 제공해야 합니다.
tool 메시지 형식은 다음과 같습니다:
<|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.”)도 함께 전달합니다. 이는 모델이 사고 과정을 이어가거나 최종 답을 제공하는 데 필요한 정보를 주기 위함입니다.
때로 모델은 호출하려는 도구에 대해 사용자에게 알리기 위해 “프리앰블”을 생성할 수 있습니다. 예를 들어 여러 도구를 호출할 계획인 경우가 그렇습니다. 이 경우, 사고 과정과 달리 최종 사용자에게 보여줄 의도의 commentary 채널 어시스턴트 메시지를 생성합니다.
<|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|>
format name은 Responses API에서 스키마에 지정할 수 있는 이름과 유사하게 동작하며, schema는 JSON 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
하지만 이 프롬프트만으로는 모델의 동작에 영향을 줄 뿐, 스키마를 완전히 준수하는 것을 보장하지는 않습니다. 이를 위해서는 별도의 문법(grammar)을 구성하고, 샘플링 중 스키마를 강제해야 합니다.
gpt-oss 모델은 학습 과정에서 정보 탐색을 위한 브라우징 도구와 결과를 개선하기 위한 파이썬 실행 도구라는 두 가지 공통 도구로 학습되었습니다.
이 기능을 구현하려 한다면, 아래 형식을 사용하여 신뢰성과 정확도를 높이세요.
이 도구들은 개발자 메시지가 아니라 시스템 메시지에 # 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|>
모델이 브라우저 액션을 호출하기로 결정하면, 함수 호출과 같은 형식을 사용하되 두 가지 차이점이 있습니다:
analysis 채널로 이루어집니다.browser.search, browser.open, browser.find가 됩니다.<|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|>
모델이 파이썬 코드를 실행하기로 결정하면, 함수 호출과 같은 형식을 사용하되 두 가지 차이점이 있습니다:
analysis 채널로 이루어집니다.python입니다