추론 토큰 (Reasoning Tokens)
추론 토큰 (Reasoning Tokens)
추론(Reasoning) 토큰은 모델이 답을 내놓기 전에 거친 사고 단계를 보여 주는 토큰이에요. 추론용으로도 불리는 이 토큰들은 모델이 "생각"한 과정을 투명하게 드러내 주는데, 비용상으로는 출력 토큰으로 취급돼요. OpenRouter는 제공자마다 제각각인 추론 토큰 조절 방식을 하나의 reasoning 파라미터로 통일해서 제공해요.
출처: https://openrouter.ai/docs/guides/best-practices/reasoning-tokens
추론 토큰 기본 동작
모델이 추론 토큰을 출력하기로 하면 기본적으로 응답에 포함돼요. 이 토큰들은 각 메시지의 reasoning 필드에 나타나며, 원하지 않으면 제외할 수 있어요. 단, OpenAI o 시리즈처럼 일부 추론 모델은 추론 토큰을 반환하지 않아요 — 대부분의 모델·제공자는 반환하지만 예외가 있으니 참고하세요.
추론 토큰 제어 (reasoning 파라미터)
reasoning 파라미터로 추론 토큰을 제어할 수 있어요.
{
"model": "your-model",
"messages": [],
"reasoning": {
// 다음 중 하나만 (둘 다는 안 됨):
"effort": "high", // "max", "xhigh", "high", "medium", "low", "minimal", "none" (OpenAI 스타일)
"max_tokens": 2000, // 특정 토큰 상한 (Anthropic 스타일)
// 선택. 기본 false. 모든 모델 지원.
"exclude": false, // true면 응답에서 추론 토큰 제외
// 또는 기본 파라미터로 추론 활성화:
"enabled": true // 기본: effort나 max_tokens에서 유추
}
}
reasoning 설정 객체는 모델별 추론 세기를 하나로 모아 제어해요. 각 옵션을 지원하는 모델과 다른 모델의 동작은 아래에서 자세히 볼게요.
모델별 추론 옵션 확인
GET /api/v1/models의 각 모델은 reasoning 객체를 포함할 수 있어요. 어떤 effort 레벨을 받는지, 추론이 필수인지 등을 알려 주죠.
{
"id": "google/gemini-3.5-flash",
"reasoning": {
"supported_efforts": ["high", "medium", "low", "minimal"],
"default_effort": "medium",
"default_enabled": true,
"mandatory": true
}
}
클라이언트 UI를 만들 때 이렇게 써요.
supported_efforts: effort 선택기를 이 값들로 한정. 내림차순(높은 것 우선)으로 반환.null이면 게이트웨이의 모든 effort 값 허용. 생략되면 모델이 effort 선택을 노출하지 않음.default_effort: 추론 활성화 시 미리 선택할 effort. 채팅 요청의reasoning.effort로 매핑. 값이"none"이면 사용자가 명시적으로 추론을 켤 때 비활성화를 미리 선택하지 말고 "기본적으로 추론 꺼짐"으로 취급.default_enabled: 사용자가reasoning.enabled를 설정하지 않았을 때의 기본 켜짐/꺼짐.supports_max_tokens:true로 존재하면 토큰 예산 컨트롤을 보여 주고reasoning.effort대신(또는 함께)reasoning.max_tokens를 전송. 토큰 예산 추론을 지원하지 않는 모델에서는 생략.mandatory:true면 비활성화 컨트롤을 숨기고effort: "none"도 보내지 않음 — 모델이 거부하기 때문.
비추론 모델과 동적 라우터 모델(openrouter/auto, openrouter/free)은 reasoning 필드를 생략해요.
추론용 Max Tokens
토큰 할당을 지원하는 모델에서는 "max_tokens": 2000처럼 추론에 쓸 최대 토큰 수를 직접 지정할 수 있어요. 현재 지원되는 모델은 다음과 같아요.
- Gemini thinking 모델
- Anthropic 추론 모델 (
reasoning.max_tokens파라미터 사용) - 일부 Alibaba Qwen thinking 모델 (
thinking_budget으로 매핑)
Alibaba는 모델마다 지원이 달라져, 개별 모델 설명에서 reasoning.max_tokens(via thinking_budget) 사용 가능 여부를 확인해야 해요. reasoning.effort만 지원하는 모델에서는 max_tokens 값이 effort 레벨을 정하는 데 쓰여요.
Reasoning Effort 레벨
OpenAI 추론 모델(o1/o3 시리즈, GPT-5 시리즈)과 Grok 모델이 지원해요.
"effort": "max"— 토큰의 가장 큰 부분을 추론에 할당 (max_tokens의 약 95%)"effort": "xhigh"— max와 동일한 할당 (약 95%)"effort": "high"— 큰 부분 할당 (약 80%)"effort": "medium"— 중간 부분 할당 (약 50%)"effort": "low"— 더 작은 부분 (약 20%)"effort": "minimal"— 훨씬 작은 부분 (약 10%)"effort": "none"— 추론 완전 비활성화
reasoning.max_tokens만 지원하는 모델에서는 위 백분율로 effort 레벨이 정해져요.
추론 토큰 제외 (exclude)
모델이 내부적으로는 추론을 쓰되 응답에는 담고 싶지 않다면 "exclude": true로 두면 돼요. 모델은 여전히 추론을 하지만 응답에서 빠져요. (참고: 각 메시지의 reasoning 필드에는 나타날 수 있어요.)
기본 설정으로 추론 활성화 (enabled)
"enabled": true는 제외 없이 "medium" effort로 추론을 켜요.
추론 보존하기 (Preserving Reasoning)
여러 턴에 걸쳐 추론 컨텍스트를 이어가려면 응답에서 받은 추론을 다시 API로 넘겨 줄 수 있어요. 두 가지 방법이 있어요.
message.reasoning(string): 어시스턴트 메시지의 문자열 필드로 일반 텍스트 추론을 전달message.reasoning_details(array): 전체 reasoning_details 블록을 전달
암호화되거나 요약된 추론 같은 특수 타입을 반환하는 모델과 작업할 때는 reasoning_details를 쓰는 게 좋아요 — 그 모델에 필요한 전체 구조를 보존하니까요. 원시 추론 문자열만 반환하는 모델에는 더 간단한 reasoning 필드를 쓸 수 있어요. reasoning_content도 별칭으로 쓸 수 있고, reasoning과 똑같이 동작해요.
여기서 핵심은 OpenAI 추론 모델(~openai/gpt-latest)과 Anthropic 추론 모델(~anthropic/claude-sonnet-latest)을 코드 구조 변경 없이 오갈 수 있다는 점이에요. 추론 블록 보존은 특히 도구 호출에서 중요해요. Claude 같은 모델이 도구를 호출할 땐 외부 정보를 기다리려고 응답 구성을 잠시 멈추는데, 도구 결과가 돌아오면 모델은 기존 응답을 계속 이어 가야 해요. 도구 사용 중 다음 두 가지 이유로 추론 블록 보존이 필요해요.
- 추론 연속성: 추론 블록이 도구 요청으로 이끈 단계별 사고를 담아요. 도구 결과와 함께 원래 추론을 넣어야 모델이 멈췄던 지점부터 계속 사고할 수 있어요.
- 컨텍스트 유지: 도구 결과가 API 구조상 user 메시지로 보여도, 실은 연속된 추론 흐름의 일부예요. 추론 블록을 보존하면 여러 API 호출에 걸쳐 이 개념적 흐름이 유지돼요.
추론 모델에 중요한 점: reasoning_details 블록을 제공할 때, 연속된 추론 블록 전체가 원래 요청 중 모델이 생성한 출력과 정확히 일치해야 해요. 이 블록들의 순서를 바꾸거나 수정할 수 없어요.
추론 컨텍스트 모드
대화 기록에 추론 항목을 되돌려 보낼 때, reasoning.context 파라미터로 모델이 접근할 추론을 제어할 수 있어요.
auto: 모델의 기본 컨텍스트 모드 사용.reasoning.context를 생략하는 것과 같아요.all_turns: 입력에 있는 모든 턴의 추론을 참조 가능. 이전 사고 사슬을 이어가길 원하는 멀티 턴 대화에 사용.current_turn: 현재 턴의 추론만 사용. 입력의 이전 추론 항목은 무시. 이전 턴 영향 없이 새 사고 패스를 원할 때 사용.
reasoning.context는 OpenAI GPT-5.6 이후에서만 지원돼요.
Responses API에서 상태를 수동 관리(출력 항목을 입력으로 다시 에코)하면서 쓴다면, reasoning.context를 include와 함께 설정해요.
{
"model": "~openai/gpt-latest",
"input": [
{ "role": "user", "content": "이 수학 문제를 단계별로 풀어 주세요: ..." },
{
"type": "reasoning",
"id": "rs_abc123",
"encrypted_content": "...",
"summary": [{ "type": "summary_text", "text": "방정식을 분석했다..." }]
},
{
"type": "message",
"role": "assistant",
"content": [{ "type": "output_text", "text": "답은 42입니다." }]
},
{ "role": "user", "content": "이제 다르게 설명해 주세요." }
],
"reasoning": {
"effort": "high",
"context": "all_turns"
},
"include": ["reasoning.encrypted_content"]
}
context의 기본값은 모델마다 다를 수 있어요. 특정 동작이 필요하면 명시적으로 설정해 주세요.
추론 모드 (Reasoning Mode)
프로 추론 변형을 제공하는 모델에서는 reasoning.mode가 어느 변형이 요청을 서빙할지 제어해요.
standard: 모델의 표준 추론 동작.reasoning.mode생략과 같아요.pro: 더 깊은 다중 패스 추론을 쓰는 프로 변형으로 요청을 라우팅. 어려운 문제용.
reasoning.mode는 OpenAI GPT-5.6 이후, OpenAI나 Azure가 서빙할 때만 지원돼요. Amazon Bedrock의 OpenAI 호환 API는 필드를 받지만 조용히 무시해서, 프로 추론은 Bedrock에서 쓸 수 없어요 — OpenRouter는 mode 선택을 존중하는 제공자에게만 프로 요청을 라우팅해요.
지원 모델에는 프로 모드를 요청하는 동등한 두 방법이 있어요.
- 표준 모델에
reasoning.mode: "pro"전송 — OpenRouter가 일치하는*-pro모델로 재라우팅 *-pro모델 목록을 직접 호출
{
"model": "~openai/gpt-latest",
"input": "소수가 무한히 많음을 증명하세요.",
"reasoning": { "mode": "pro" }
}
mode는effort와 독립적이에요.mode: "pro"를 어떤 effort 레벨과도 조합할 수 있어요.- 프로 모드는 표준 모드와 같은 토큰당 요율로 청구되지만, 보통 더 많은 토큰을 소모해요.
Reasoning Details API 구조
추론 모델이 응답을 만들면 그 추론 정보는 reasoning_details 배열로 표준화된 구조에 담겨요. 이 배열은 스트리밍/비스트리밍에 따라 위치가 달라져요.
- 비스트리밍 응답:
choices[].message.reasoning_details - 스트리밍 응답: 각 청크의
choices[].delta.reasoning_details
모든 추론 상세 객체는 공통 필드를 공유해요.
id(string | null): 추론 상세의 고유 식별자format(string): 추론 상세 형식. 가능한 값 —"unknown","openai-responses-v1","azure-openai-responses-v1","bedrock-openai-responses-v1","bedrock-xai-responses-v1","xai-responses-v1","meta-responses-v1","anthropic-claude-v1"(기본),"google-gemini-v1"index(number, 선택): 추론 상세의 순차 인덱스
추론 상세 타입은 세 가지가 있어요.
1. 요약 타입 (reasoning.summary) — 사고 과정의 고수준 요약을 담아요.
{
"type": "reasoning.summary",
"summary": "모델은 핵심 제약을 먼저 식별한 뒤 가능한 해법을 평가했다...",
"id": "reasoning-summary-1",
"format": "anthropic-claude-v1",
"index": 0
}
2. 암호화 타입 (reasoning.encrypted) — 편집되거나 보호될 수 있는 암호화 추론 데이터.
{
"type": "reasoning.encrypted",
"data": "eyJlbm...0ifQ==",
"id": "reasoning-encrypted-1",
"format": "anthropic-claude-v1",
"index": 1
}
3. 텍스트 타입 (reasoning.text) — 선택적 서명 검증이 있는 원시 텍스트 추론.
{
"type": "reasoning.text",
"text": "단계별로 생각해 보자:\n1. 먼저 사용자 질문을 이해해야 한다...",
"signature": "sha256:abc123def456...",
"id": "reasoning-text-1",
"format": "anthropic-claude-v1",
"index": 2
}
스트리밍에서는 각 추론 상세 청크가 준비되는 대로 전송돼요. 각 청크의 reasoning_details 배열에는 하나 이상의 추론 객체가 들어갈 수 있고, 암호화 추론은 [REDACTED]로 보일 수 있어요. 전체 추론 시퀀스는 모든 청크를 순서대로 이어 붙여 구성돼요.
레거시 파라미터
하위 호환을 위해 OpenRouter는 여전히 다음을 지원해요.
include_reasoning: true—reasoning: {}와 동일include_reasoning: false—reasoning: { exclude: true }와 동일
새로운 통합 reasoning 파라미터를 쓰는 걸 권장해요. 제어가 더 세밀하고 미래 호환성이 좋아요.
제공자별 추론 구현
Anthropic 모델과 추론 토큰
최신 Claude 모델(~anthropic/claude-sonnet-latest)은 추론 토큰을 처리·반환해요. Anthropic 모델에서는 통합 reasoning 파라미터를 effort나 max_tokens와 함께 써서 만 추론을 켤 수 있어요.
reasoning.max_tokens를 쓰면 그 값이 최소 1024 토큰으로 직접 사용돼요. reasoning.effort를 쓰면 budget_tokens가 max_tokens 값에서 계산돼요.
추론 토큰 할당은 최대 128,000, 최소 1024 토큰으로 제한돼요. budget_tokens 공식은 다음과 같아요.
budget_tokens = max(min(max_tokens * {effort_ratio}, 128000), 1024)
effort_ratio는 max/xhigh가 0.95, high가 0.8, medium이 0.5, low가 0.2, minimal이 0.1이에요. 중요: 사고 후에도 최종 응답에 사용할 토큰이 남도록 max_tokens는 추론 예산보다 엄격히 커야 해요.
토큰 사용·청구: 추론 토큰은 청구 목적으로 출력 토큰으로 집계돼요. 추론을 쓰면 토큰 사용량은 늘지만 모델 응답 품질이 크게 좋아질 수 있어요.
요약된 사고 (Summarized Thinking)
thinking.display 필드를 지원하는 Claude 모델에서 OpenRouter는 기본적으로 요약된 사고(thinking.display: 'summarized')를 써요. 그래서 Anthropic이 기본적으로 사고를 생략하는 최신 모델에서도 추론 흔적을 잃지 않아요. display 설정은 응답에서 보이는 사고 흔적만 제어하고, 모델은 어느 쪽이든 같은 수의 토큰을 소비해요 — 청구는 모델이 실제 생성한 토큰 기준이죠. 보이는 요약은 압축되어 usage에 보고된 추론 토큰 수보다 적을 수 있어요.
'summarized'(기본): 압축된 사고 요약 반환'omitted': 사고 흔적 미반환
Google Gemini 3 모델과 thinking 레벨
Gemini 3 모델(google/gemini-3.1-pro-preview, google/gemini-3-flash-preview)은 Gemini 2.5가 쓰던 thinkingBudget API 대신 Google의 thinkingLevel API를 써요. OpenRouter는 reasoning.effort를 Google의 thinkingLevel 값으로 직접 매핑해요.
OpenRouter reasoning.effort |
Google thinkingLevel |
|---|---|
"minimal" |
"minimal" |
"low" |
"low" |
"medium" |
"medium" |
"high" |
"high" |
"xhigh" |
"high" (하향 매핑) |
thinkingLevel을 쓸 때 실제 소비되는 추론 토큰 수는 Google이 내부적으로 결정해요. 레벨별 토큰 한도 브레이크포인트가 공개 문서화되어 있지 않아요. 예를 들어 effort: "low"로 설정해도 작업 복잡도에 따라 추론 토큰이 몇 백 개가 될 수 있어요 — Google의 thinking 레벨 구현 방식이라 예상된 동작이에요.
모델이 특정 effort 레벨을 지원하지 않으면(예: low와 high만 지원), OpenRouter는 요청한 effort를 가장 가까운 지원 레벨로 매핑해요.
reasoning.max_tokens를 명시하면 OpenRouter가 Google API에 thinkingBudget으로 넘겨줘요. 하지만 Gemini 3에서는 Google이 이 예산을 내부적으로 thinkingLevel로 매핑하므로 정밀한 토큰 제어는 되지 않아요. 실제 토큰 소비는 여전히 Google의 thinkingLevel 구현이 결정해요.