우리는 Claude 시리즈 모델을 위해 특별히 깊은 최적화를 적용하여 OpenAI 호환 인터페이스를 업그레이드했습니다. 이제 사고와 캐싱을 보다 정확하고 편리하게 제어할 수 있습니다. 다중 턴 대화에서의 교차 사고는 이제 사용자 친화적이며 추가 매개변수 없이 원활한 통합을 가능하게 합니다. 또한 Anthropic에서 제공하는 베타 기능을 활성화할 수 있습니다.
1. 모델 사고 (확장된 사고)
1.1 교차 사고의 장점
교차 사고가 활성화되지 않은 경우, 모델은 어시스턴트 턴의 시작에서만 사고를 수행하며; 이후 응답은 도구 결과를 받은 직후에 생성되며 새로운 사고 블록을 생성하지 않습니다:
User → [Thinking] → Tool Call → Tool Result → Response
교차 사고가 활성화되면, 모델은 도구 결과를 받을 때마다 새로운 사고 블록을 삽입하여 추론의 체인을 형성합니다:
User → [Thinking] → Tool Call → Tool Result → [Thinking] → Response
↑ 교차 사고
이로 인해 모델은:
- 도구 결과를 기반으로 2차 추론을 수행할 수 있으며, 단순히 출력을 연결하는 것이 아닙니다.
- 여러 도구 호출 간의 추론 체인을 형성할 수 있으며, 각 결정은 이전 단계의 분석을 기반으로 합니다.
참고: Anthropic 교차 사고
1.2 사고 활성화
사고를 활성화하는 방법은 네 가지가 있으며, 그 중 하나를 선택할 수 있습니다:
| 방법 | 예시 | 설명 |
|---|---|---|
reasoning_effort |
"reasoning_effort": "low" |
OpenAI 표준 매개변수, 요청 본문의 최상위에 배치 |
reasoning.effort |
"reasoning": {"effort": "low"} |
이전 방법과 동등하며, 추론 객체 내에 배치 |
reasoning.max_tokens |
"reasoning": {"max_tokens": 1024} |
사고를 위한 최대 토큰 수를 정확하게 제어 |
모델 이름에 -think 추가 |
"model": "claude-sonnet-4-5-think" |
가장 간단한 방법으로, 추가 매개변수가 필요 없음 |
우선 순위 (여러 방법이 사용될 때):reasoning_effort>reasoning.max_tokens>reasoning.effort>-think접미사
노력의 가능한 값: minimal / low / medium / high / xhigh
1.3 사고 반환
응답 메시지에는 두 개의 새로운 필드가 포함됩니다:
reasoning_content: 사고 내용 (문자열), 쉽게 표시할 수 있습니다.reasoning_details: 사고에 대한 완전한 구조화된 정보, 다중 턴 대화에서 그대로 반환되어야 하며; 내부 구조는 제공자에 따라 다를 수 있습니다.
비스트리밍 예시 (관련 없는 필드를 생략):
{
"choices": [{
"message": {
"role": "assistant",
"content": "안녕하세요! 오늘 무엇을 도와드릴까요?",
"reasoning_content": "사용자가 그냥 인사하고 있습니다...",
"reasoning_details": {
"type": "thinking",
"thinking": "사용자가 그냥 인사하고 있습니다...",
"signature": "Er8CCkYI..."
}
}
}]
}
스트리밍 응답에서는 사고 내용이 delta.reasoning_content 및 delta.reasoning_details를 통해 청크로 전송됩니다. 전체 스트리밍 연결 논리는 아래의 전체 예제를 참조하십시오.
1.4 다중 턴 대화에서 사고 유지 (교차 사고는 내장되어 있으며, 추가 매개변수가 필요 없음)
모델이 다중 턴 대화에서 사고 능력을 계속할 수 있도록 하려면, 이전에 반환된 reasoning_details를 그대로 다음 턴의 어시스턴트 메시지에 넣으면 됩니다:
messages = [
{"role": "user", "content": "보스턴의 날씨는 어떤가요? 그럼 무엇을 입어야 할지 추천해 주세요."},
{
"role": "assistant",
"content": response.choices[0].message.content,
"tool_calls": response.choices[0].message.tool_calls,
"reasoning_details": response.choices[0].message.reasoning_details,
},
{
"role": "tool",
"tool_call_id": "toolu_xxx",
"content": '{"temperature": 45, "condition": "rainy"}',
}
]
AIHubMix는 요청에서 역사적 사고 정보를 감지하면 자동으로 교차 사고를 활성화하여 모델이 도구 호출 결과를 받은 후 추가 매개변수 없이 깊은 추론을 계속할 수 있도록 합니다.
1.5 전체 예시
다음 두 예시는 전체 다중 턴 도구 호출 + 교차 사고 프로세스를 보여줍니다: 사용자 문의 → 모델이 사고하고 도구를 호출 → 도구 결과 주입 ( reasoning_details 보존) → 모델의 교차 사고가 최종 응답을 제공합니다.
비스트리밍 · 교차 사고
import os
import json
from openai import OpenAI
client = OpenAI(
base_url="https://aihubmix.com/v1",
api_key=os.environ.get("AIHUBMIX_API_KEY", "sk-***"),
)
# ── 도구 정의 ───────────────────────────────────────────
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "위치의 현재 날씨를 가져옵니다.",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string", "description": "도시 이름"}},
"required": ["location"]
}
}
}]
# ── 모의 도구 실행 ───────────────────────────────────────
WEATHER_DB = {
"boston": {"temperature": "45°F (7°C)", "condition": "rainy", "humidity": "85%", "wind": "15 mph NE"},
"tokyo": {"temperature": "72°F (22°C)", "condition": "sunny", "humidity": "45%", "wind": "5 mph S"},
}
def execute_tool(name: str, args: dict) -> str:
if name == "get_weather":
key = next((k for k in WEATHER_DB if k in args.get("location", "").lower()), None)
return json.dumps(WEATHER_DB.get(key, {"temperature": "65°F", "condition": "clear"}))
return "{}"
# ── 다중 턴 대화 루프 ─────────────────────────────
messages = [
{"role": "user", "content": "보스턴의 날씨는 어떤가요? 그럼 무엇을 입어야 할지 추천해 주세요."}
]
turn = 0
while True:
turn += 1
print(f"\n── 턴 {turn} ──")
response = client.chat.completions.create(
model="claude-sonnet-4-5",
messages=messages,
tools=tools,
extra_body={"reasoning": {"max_tokens": 2000}},
)
msg = response.choices[0].message
# 사고 과정 출력
if msg.reasoning_content:
label = "교차 사고" if turn > 1 else "사고"
print(f"[{label}] {msg.reasoning_content}")
# 응답 내용 출력
if msg.content:
print(f"[응답] {msg.content}")
# 도구 호출 출력
if msg.tool_calls:
for tc in msg.tool_calls:
print(f"[도구 호출: {tc.function.name}] {tc.function.arguments}")
# 어시스턴트 메시지 구축, reasoning_details 보존 (중요!)
assistant_msg = {"role": "assistant", "content": msg.content}
if msg.tool_calls:
assistant_msg["tool_calls"] = msg.tool_calls
if msg.reasoning_details:
assistant_msg["reasoning_details"] = msg.reasoning_details # 수정하지 않고 전달
messages.append(assistant_msg)
# 도구 호출이 없으면 대화가 끝남
if not msg.tool_calls:
break
# 도구 실행 및 결과를 메시지에 추가
for tc in msg.tool_calls:
args = json.loads(tc.function.arguments)
result = execute_tool(tc.function.name, args)
print(f"[도구 결과: {tc.function.name}] {result}")
messages.append({"role": "tool", "tool_call_id": tc.id, "content": result})
스트리밍 · 교차 사고
import os
import sys
import json
from openai import OpenAI
client = OpenAI(
base_url="https://aihubmix.com/v1",
api_key=os.environ.get("AIHUBMIX_API_KEY", "sk-***"),
)
# ── 도구 정의 및 모의 실행 ─────────────────────────
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "위치의 현재 날씨를 가져옵니다.",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string", "description": "도시 이름"}},
"required": ["location"]
}
}
}]
WEATHER_DB = {
"boston": {"temperature": "45°F (7°C)", "condition": "rainy", "humidity": "85%", "wind": "15 mph NE"},
"tokyo": {"temperature": "72°F (22°C)", "condition": "sunny", "humidity": "45%", "wind": "5 mph S"},
}
def execute_tool(name: str, args: dict) -> str:
if name == "get_weather":
key = next((k for k in WEATHER_DB if k in args.get("location", "").lower()), None)
return json.dumps(WEATHER_DB.get(key, {"temperature": "65°F", "condition": "clear"}))
return "{}"
# ── 스트림 응답 수집기 ────────────────────────────────
def stream_and_collect(turn: int, **kwargs):
"""스트림 응답, 사고/내용을 실시간으로 출력하고, reasoning_details/tool_calls를 누적합니다."""
rd = {} # 누적된 reasoning_details
content = "" # 누적된 응답 텍스트
tc_map = {} # 누적된 tool_calls (인덱스별)
cur = "none" # 현재 출력 섹션: none / thinking / content
stream = client.chat.completions.create(stream=True, **kwargs)
for chunk in stream:
if not chunk.choices:
continue
delta = chunk.choices[0].delta
# ── 사고 처리 ──
rd_delta = getattr(delta, "reasoning_details", None)
if rd_delta and isinstance(rd_delta, dict):
for k, v in rd_delta.items():
if k == "type":
rd[k] = v
elif isinstance(v, str):
rd[k] = rd.get(k, "") + v
elif v is not None:
rd[k] = v
# 사고 청크를 실시간으로 출력
thinking_chunk = rd_delta.get("thinking", "")
if thinking_chunk:
if cur != "thinking":
cur = "thinking"
label = "교차 사고" if turn > 1 else "사고"
sys.stdout.write(f"\n[{label}] ")
sys.stdout.write(thinking_chunk)
sys.stdout.flush()
# ── 내용 처리 ──
if delta.content:
if cur != "content":
if cur == "thinking":
sys.stdout.write("\n")
cur = "content"
sys.stdout.write("\n[응답] ")
sys.stdout.write(delta.content)
sys.stdout.flush()
content += delta.content
# ── 도구 호출 처리 ──
for tc in delta.tool_calls or []:
i = tc.index
if i not in tc_map:
tc_map[i] = {"id": "", "type": "function",
"function": {"name": "", "arguments": ""}}
if tc.id:
tc_map[i]["id"] = tc.id
if tc.function:
tc_map[i]["function"]["name"] += tc.function.name or ""
tc_map[i]["function"]["arguments"] += tc.function.arguments or ""
# 현재 출력 섹션 종료
if cur in ("thinking", "content"):
sys.stdout.write("\n")
tool_calls = [tc_map[i] for i in sorted(tc_map)] if tc_map else None
return {
"content": content or None,
"reasoning_details": rd or None,
"tool_calls": tool_calls,
}
# ── 다중 턴 대화 루프 ─────────────────────────────
messages = [
{"role": "user", "content": "보스턴의 날씨는 어떤가요? 그럼 무엇을 입어야 할지 추천해 주세요."}
]
turn = 0
while True:
turn += 1
print(f"\n── 턴 {turn} ──")
result = stream_and_collect(
turn,
model="claude-sonnet-4-5",
messages=messages,
tools=tools,
extra_body={"reasoning": {"max_tokens": 2000}},
)
# 도구 호출 출력
if result["tool_calls"]:
for tc in result["tool_calls"]:
print(f"[도구 호출: {tc['function']['name']}] {tc['function']['arguments']}")
# 어시스턴트 메시지 구축, reasoning_details 보존 (중요!)
assistant_msg = {"role": "assistant", "content": result["content"]}
if result["tool_calls"]:
assistant_msg["tool_calls"] = result["tool_calls"]
if result["reasoning_details"]:
assistant_msg["reasoning_details"] = result["reasoning_details"] # 수정하지 않고 전달
messages.append(assistant_msg)
# 도구 호출이 없으면 대화가 끝남
if not result["tool_calls"]:
break
# 도구 실행 및 결과를 메시지에 추가
for tc in result["tool_calls"]:
args = json.loads(tc["function"]["arguments"])
tool_result = execute_tool(tc["function"]["name"], args)
print(f"[도구 결과: {tc['function']['name']}] {tool_result}")
messages.append({"role": "tool", "tool_call_id": tc["id"], "content": tool_result})
1.6 사고 강도 매핑 규칙
노력 모드:
- Opus 4.6 / Sonnet 4.6 이상: Anthropic의 기본 적응형 사고 노력 수준에 매핑됩니다.
- 기타 모델:
budget_tokens의 공식을 사용하여 계산됩니다:
budget_tokens = max(min(max_tokens × effort_ratio, 128000), 1024)
| 노력 | 노력 비율 |
|---|---|
| xhigh | 0.95 |
| high | 0.80 |
| medium | 0.50 |
| low | 0.20 |
| minimal | 0.10 |
적응형 사고 노력 매핑:
| 들어오는 노력 | Opus 4.6 | Sonnet 4.6 |
|---|---|---|
| xhigh | max | high |
| high | high | high |
| medium | medium | medium |
| low | low | low |
| minimal | low | low |
max_tokens 모드: Anthropic의 budget_tokens로 직접 할당됩니다.
-think 접미사: Opus/Sonnet 4.6+는 적응형 사고를 사용하며 (effort=medium); 다른 모델은 budget_tokens = min(10240, max_tokens - 1)로 설정되며, 기본 max_tokens는 4096입니다.
2. 프롬프트 캐싱
Claude 모델에 요청할 때 프롬프트 캐싱을 사용할 수 있습니다. 메시지에 cache_control 중단점을 설정하여, 역할 카드, RAG 데이터, 책 장 등과 같은 큰 텍스트 블록을 캐시하여 재사용할 수 있으며, 이후 요청이 캐시에 직접 접근하여 비용을 크게 줄일 수 있습니다.
Claude 공식 문서: 프롬프트 캐싱
2.1 캐싱 비용
| 작업 | 가격 배수 (원본 입력 가격 대비) |
|---|---|
| 캐시 쓰기 (5분 TTL) | 1.25x |
| 캐시 쓰기 (1시간 TTL) | 2x |
| 캐시 읽기 | 0.1x |
2.2 지원되는 모델 및 최소 캐시 길이
| 모델 | 최소 캐시 토큰 수 |
|---|---|
| Claude Opus 4.8 | 1024 |
| Claude Opus 4.7 | 2048 |
| Claude Opus 4.6 / Opus 4.5 | 4096 |
| Claude Sonnet 4.6 / Sonnet 4.5 / Opus 4.1 / Opus 4 / Sonnet 4 / Sonnet 3.7 (사용 중단) | 1024 |
| Claude Haiku 4.5 | 4096 |
| Claude Haiku 3.5 (사용 중단) / Haiku 3 | 2048 |
중단점 수량 제한: 요청당 최대 4 개의 cache_control 중단점.2.3 캐시 TTL
| TTL | 구문 | 적용 시나리오 |
|---|---|---|
| 5분 (기본값) | "cache_control": {"type": "ephemeral"} |
짧은 세션, 일상적인 요청 |
| 1시간 | "cache_control": {"type": "ephemeral", "ttl": "1h"} |
긴 세션, 반복적인 캐시 쓰기를 피하기 위해 |
1시간 TTL에 대한 쓰기 비용은 더 높지만, 긴 세션에서 반복적인 쓰기를 줄여 전체 비용을 절감할 수 있습니다. Claude 4.5 이상 모든 모델은 모든 제공자(Anthropic, Amazon Bedrock, Google Vertex AI 포함)에서 1시간 TTL을 지원합니다.
2.4 사용법
시스템, 사용자(이미지 포함), 도구에서 cache_control 필드를 사용하여 캐시 중단점을 설정할 수 있습니다. 다음 예시는 큰 텍스트 블록을 생략하고 키 구조만 보여줍니다.
시스템 메시지 캐싱 (기본 5분 TTL):
{
"model": "claude-opus-4-5",
"messages": [
{
"role": "system",
"content": [
{"type": "text", "text": "당신은 AI 어시스턴트입니다."},
{
"type": "text",
"text": "(긴 맥락)",
"cache_control": {"type": "ephemeral"}
}
]
},
{
"role": "user",
"content": [{"type": "text", "text": "안녕하세요"}]
}
]
}
사용자 메시지 캐싱 (1시간 TTL):
{
"model": "claude-opus-4-5",
"messages": [
{
"role": "system",
"content": [{"type": "text", "text": "당신은 AI 어시스턴트입니다."}]
},
{
"role": "user",
"content": [
{
"type": "text",
"text": "(긴 맥락)",
"cache_control": {"type": "ephemeral", "ttl": "1h"}
},
{"type": "text", "text": "안녕하세요"}
]
}
]
}
이미지 메시지 캐싱:
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {"detail": "auto", "url": "data:image/jpeg;base64,/9j/4AAQ..."},
"cache_control": {"type": "ephemeral"}
},
{"type": "text", "text": "이게 뭐죠?"}
]
}
도구 정의 캐싱:
cache_control는 도구 객체의 최상위에 배치됩니다 ( type 및 function와 함께):
{
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "위치의 현재 날씨를 가져옵니다.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
},
"cache_control": {"type": "ephemeral", "ttl": "1h"}
}]
}
2.5 캐시 상태 보기
응답의 usage는 claude_cache_tokens_details를 반환하여 상세한 캐시 정보를 기록합니다:
첫 번째 요청 (캐시 생성):
{
"usage": {
"prompt_tokens": 22,
"completion_tokens": 890,
"total_tokens": 912,
"claude_cache_tokens_details": {
"cache_creation_input_tokens": 6266,
"cache_read_input_tokens": 0,
"cache_write_5_minutes_input_tokens": 6266,
"cache_write_1_hour_input_tokens": 0
}
}
}
후속 요청 (캐시 적중):
{
"usage": {
"prompt_tokens": 22,
"completion_tokens": 810,
"total_tokens": 832,
"prompt_tokens_details": {
"cached_tokens": 6266
},
"claude_cache_tokens_details": {
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 6266,
"cache_write_5_minutes_input_tokens": 0,
"cache_write_1_hour_input_tokens": 0
}
}
}
| 필드 | 의미 |
|---|---|
cache_creation_input_tokens |
이 요청에서 캐시에 기록된 토큰 수 |
cache_read_input_tokens |
이 요청에서 캐시에서 읽은 토큰 수 |
cache_write_5_minutes_input_tokens |
5분 TTL 캐시에 기록된 토큰 수 |
cache_write_1_hour_input_tokens |
1시간 TTL 캐시에 기록된 토큰 수 |
prompt_tokens_details.cached_tokens |
캐시가 적중했을 때의 캐시된 토큰 수, OpenAI 형식과 호환됨 |
3. anthropic-beta 요청 헤더
HTTP 헤더 anthropic-beta를 통해 Claude 모델의 베타 기능을 활성화할 수 있으며, AIHubMix는 이를 Anthropic API로 전달합니다.
사용법
요청 헤더에 anthropic-beta를 추가하고, 값으로 해당 베타 기능 식별자를 설정합니다:
curl "https://aihubmix.com/v1/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AIHUBMIX_API_KEY" \
-H "anthropic-beta: context-1m-2025-08-07" \
-d '{
"model": "claude-opus-4-5",
"messages": [
{
"role": "system",
"content": [
{"type": "text", "text": "당신은 AI 어시스턴트입니다."},
{
"type": "text",
"text": "(긴 맥락)",
"cache_control": {"type": "ephemeral"}
}
]
},
{"role": "user", "content": [{"type": "text", "text": "안녕하세요"}]}
]
}'
특정 사용 가능한 베타 식별자는 Anthropic API 문서를 참조하십시오.
최종 업데이트: 2026-06-01