프롬프트 엔지니어링 기초 (5) - Reasoning: thinking과 effort로 추론 조절하기
단계적 사고 유도 프롬프트가 adaptive thinking으로 바뀐 지점을 정리하고 effort 단계별 정확도와 토큰 사용량을 직접 측정합니다.
프롬프트 엔지니어링 기초 시리즈의 5편입니다. 전체 목차는 0편에 있습니다.
단계별로 생각해라
복잡한 문제에서 답을 곧장 내놓게 하는 대신 풀이 과정을 먼저 쓰게 하면 정확도가 올라갑니다. 이 방법을 chain-of-thought라고 부르고, 오랫동안 프롬프트 엔지니어링의 핵심 기법이었습니다.
1
2
prompt = """카페에 사과가 23개 있었다. 오전에 8개를 팔고, 오후에 15개를 더 들여왔다.
지금 사과는 몇 개인가? 풀이 과정을 단계별로 쓴 다음 마지막 줄에 '답: N' 형식으로 답해."""
지금도 유효한 기법이지만, 자리는 달라졌습니다. Claude 5 계열은 이 과정을 모델이 내부에서 알아서 수행합니다. 프롬프트로 유도하는 대신 API 파라미터로 조절하는 것이 기본형이 되었습니다.
adaptive thinking
thinking은 모델이 답하기 전에 별도로 사고하는 단계입니다. claude-opus-5는 이 기능이 기본으로 켜져 있습니다.
1
2
3
4
5
6
7
8
9
10
11
message = client.messages.create(
model="claude-opus-5",
max_tokens=8192,
thinking={"type": "adaptive", "display": "summarized"},
messages=[{"role": "user", "content": prompt}],
)
for block in message.content:
if block.type == "thinking":
print("[사고 요약]", block.thinking[:200])
print("[답]", text_of(message))
adaptive는 얼마나 생각할지를 모델이 문제마다 정한다는 뜻입니다. 예전 방식인 {"type": "enabled", "budget_tokens": 8000}은 Claude 5 계열에서 400으로 거절됩니다. 사고 토큰 수를 직접 지정하는 개념 자체가 사라졌습니다.
display는 요약을 돌려받을지 정합니다. 기본값 omitted에서는 thinking block이 오되 내용이 빈 문자열입니다. 사고 과정을 화면에 흘려보내는 제품이라면 summarized를 명시해야 합니다. 어느 설정이든 원본 사고 과정은 반환되지 않고, 토큰 과금은 동일합니다.
effort
effort는 사고 깊이와 전체 토큰 사용량을 정하는 다이얼입니다. output_config 안에 넣습니다.
1
2
3
4
5
6
message = client.messages.create(
model="claude-opus-5",
max_tokens=8192,
output_config={"effort": "low"},
messages=[{"role": "user", "content": prompt}],
)
| 단계 | 쓰는 곳 |
|---|---|
low | 분류, 추출, 짧은 응답처럼 정해진 작업 |
medium | 비용을 줄이면서 품질을 유지하고 싶을 때 |
high | 기본값. 대부분의 작업 |
xhigh | 코딩과 agent 작업 |
max | 비용보다 정확도가 중요한 어려운 문제 |
기본값은 high입니다. 아무 설정도 하지 않으면 high로 돌아간다는 뜻이므로, 분류처럼 단순한 작업을 대량으로 돌린다면 low로 내리는 것만으로 토큰이 크게 줄어듭니다.
실습: effort별 정확도와 토큰
계산이 필요한 문제 여섯 개로 low와 high를 비교합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
PROBLEMS = [
("사과 23개 중 8개를 팔고 15개를 더 들여왔다. 지금 몇 개인가?", "30"),
("정가 42000원인 상품을 25% 할인하면 얼마인가?", "31500"),
("3명이 7500원씩 냈고 총 21000원을 썼다. 남은 돈은?", "1500"),
("월요일 배송 시작, 영업일 기준 3일 걸리면 도착 요일은?", "목요일"),
("한 상자에 12개, 5상자를 사면 3개를 더 준다. 총 몇 개인가?", "63"),
("15000원 이상 무료배송이다. 4900원 상품 3개를 사면 배송비를 내는가?", "낸다"),
]
def measure(effort: str):
correct = 0
tokens = 0
for question, answer in PROBLEMS:
message = client.messages.create(
model="claude-opus-5",
max_tokens=8192,
output_config={"effort": effort},
system="계산 문제에 답한다. 마지막 줄에 '답: N' 형식으로 결론만 다시 쓴다.",
messages=[{"role": "user", "content": question}],
)
out = text_of(message)
correct += answer in out.splitlines()[-1]
tokens += message.usage.output_tokens
print(f"effort={effort}: {correct}/{len(PROBLEMS)} 정답, 출력 토큰 {tokens}")
measure("low")
measure("high")
# effort=low: 5/6 정답, 출력 토큰 412
# effort=high: 6/6 정답, 출력 토큰 1834
숫자는 실행마다 달라지지만 관계는 유지됩니다. high가 정확하고 low가 쌉니다. 실무에서 정할 것은 “어느 쪽이 좋은가”가 아니라 “이 작업에서 한 건 더 맞히는 값이 토큰 네 배어치인가”입니다. 그래서 이 측정을 프롬프트 후보마다 돌려두면 모델 설정을 감이 아니라 표로 정할 수 있습니다.
프롬프트로 하는 추론 유도는 언제 쓰나
thinking이 자동이 되었어도 프롬프트가 필요한 경우는 남습니다.
effort를 낮게 고정해야 하는데 문제는 복잡할 때 지연 시간 제약 때문에low를 써야 한다면, “여러 단계를 거쳐야 하는 문제다. 답하기 전에 단계별로 정리해라” 같은 문장으로 보완합니다- 사고 과정 자체가 결과물일 때 사용자에게 풀이를 보여줘야 한다면 thinking 요약이 아니라 응답 본문에 단계를 쓰게 해야 합니다
- thinking이 없는 모델을 쓸 때 예전 세대 모델에서는 여전히 프롬프트가 유일한 수단입니다
반대로 “생각하지 마라”, “추론하지 마라” 같은 지시는 넣지 않습니다. 사고 태그가 응답에 새어 나오는 문제를 오히려 늘립니다.
thinking을 끄는 경우
지연 시간이나 비용 때문에 꺼야 한다면 이렇게 씁니다.
1
2
thinking={"type": "disabled"},
output_config={"effort": "high"}, # xhigh, max와 함께 쓰면 400
claude-opus-5에서 thinking을 끄는 것은 effort가 high 이하일 때만 허용됩니다. 그리고 끈 상태에서는 두 가지 증상이 알려져 있습니다. 도구 호출을 tool_use block이 아니라 그냥 텍스트로 써버려 호출이 조용히 실행되지 않는 경우, 그리고 <thinking> 같은 내부 태그가 응답에 섞이는 경우입니다.
그래서 비용을 줄이는 첫 번째 수단은 thinking을 끄는 것이 아니라 effort를 낮추는 것입니다. low나 medium으로 내리는 편이 안전하고, 효과도 대부분 같습니다.
정리
| 예전 방식 | 지금 방식 |
|---|---|
| “단계별로 생각해 보자”를 프롬프트에 추가 | thinking={"type": "adaptive"} (Opus 5는 기본 켜짐) |
budget_tokens로 사고량 지정 | output_config={"effort": ...} (400으로 거절됨) |
temperature로 일관성 조절 | effort와 프롬프트 제약 (파라미터 제거됨) |
| 사고 과정을 응답에서 파싱 | display: "summarized"로 요약 block 수신 |
다음 편에서 지금까지 만든 프롬프트들을 한 번에 채점하는 평가 스크립트를 작성합니다.