추론 토큰 (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로 넘겨 줄 수 있어요. 두 가지 방법이 있어요.

  1. message.reasoning (string): 어시스턴트 메시지의 문자열 필드로 일반 텍스트 추론을 전달
  2. 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.contextinclude와 함께 설정해요.

{
  "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 선택을 존중하는 제공자에게만 프로 요청을 라우팅해요.

지원 모델에는 프로 모드를 요청하는 동등한 두 방법이 있어요.

  1. 표준 모델에 reasoning.mode: "pro" 전송 — OpenRouter가 일치하는 *-pro 모델로 재라우팅
  2. *-pro 모델 목록을 직접 호출
{
  "model": "~openai/gpt-latest",
  "input": "소수가 무한히 많음을 증명하세요.",
  "reasoning": { "mode": "pro" }
}
  • modeeffort와 독립적이에요. 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: truereasoning: {}와 동일
  • include_reasoning: falsereasoning: { exclude: true }와 동일

새로운 통합 reasoning 파라미터를 쓰는 걸 권장해요. 제어가 더 세밀하고 미래 호환성이 좋아요.

제공자별 추론 구현

Anthropic 모델과 추론 토큰

최신 Claude 모델(~anthropic/claude-sonnet-latest)은 추론 토큰을 처리·반환해요. Anthropic 모델에서는 통합 reasoning 파라미터를 effortmax_tokens와 함께 써서 추론을 켤 수 있어요.

reasoning.max_tokens를 쓰면 그 값이 최소 1024 토큰으로 직접 사용돼요. reasoning.effort를 쓰면 budget_tokensmax_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 레벨을 지원하지 않으면(예: lowhigh만 지원), OpenRouter는 요청한 effort를 가장 가까운 지원 레벨로 매핑해요.

reasoning.max_tokens를 명시하면 OpenRouter가 Google API에 thinkingBudget으로 넘겨줘요. 하지만 Gemini 3에서는 Google이 이 예산을 내부적으로 thinkingLevel로 매핑하므로 정밀한 토큰 제어는 되지 않아요. 실제 토큰 소비는 여전히 Google의 thinkingLevel 구현이 결정해요.

더 알아보기