프롬프트 엔지니어링 기초 (1) - First Call: 요청과 응답 뜯어보기
messages 요청의 항목별 의미와 응답의 content block 구조, max_tokens와 thinking의 관계, usage로 토큰과 비용을 확인하는 방법을 다룹니다.
프롬프트 엔지니어링 기초 시리즈의 1편입니다. 전체 목차는 0편에 있습니다.
요청 한 벌
프롬프트를 다듬기 전에 요청이 어떤 항목으로 이루어지는지 확인합니다.
1
2
3
4
5
6
7
8
9
10
11
12
from anthropic import Anthropic
client = Anthropic()
message = client.messages.create(
model="claude-opus-5", # ①
max_tokens=1024, # ②
system="너는 데이터 분석가다. 통계 용어를 풀어서 설명한다.", # ③
messages=[ # ④
{"role": "user", "content": "p-value가 무엇인지 설명해줘."}
],
)
- ①
model: 호출할 모델의 ID입니다. 문자열이 정확해야 하며, 오타는 404로 돌아옵니다 - ②
max_tokens: 이번 응답에서 생성할 수 있는 토큰의 상한입니다. 필수 항목이고, 뒤에서 다시 다룹니다 - ③
system: 모델의 역할과 규칙을 정하는 자리입니다. 대화 내내 고정되는 내용을 넣습니다 - ④
messages: 실제 대화입니다.user와assistant가 번갈아 나오며 첫 항목은 반드시user입니다
system과 messages는 자리만 다른 것이 아닙니다. 모델이 읽는 순서는 tools → system → messages이고, 앞쪽일수록 잘 지켜지며 캐시에도 유리합니다(7편). 매번 바뀌는 값은 messages에, 고정된 규칙은 system에 두는 것이 기본형입니다.
응답은 텍스트가 아니라 block 목록
응답에서 가장 자주 틀리는 부분이 여기입니다. message.content는 문자열이 아니라 block의 목록입니다.
1
2
3
4
5
for block in message.content:
print(block.type)
# thinking
# text
claude-opus-5는 thinking이 기본으로 켜져 있어서, 응답 텍스트 앞에 thinking block이 먼저 들어옵니다. 그래서 예제 코드에서 흔히 보이는 message.content[0].text는 이 모델에서 그대로 쓰면 실패합니다. thinking block에는 text 속성이 없습니다.
시리즈 내내 아래 헬퍼를 씁니다.
1
2
3
def text_of(message) -> str:
"""응답에서 사람이 읽을 텍스트만 이어 붙인다."""
return "".join(b.text for b in message.content if b.type == "text")
thinking 내용을 실제로 보고 싶다면 display를 켜면 요약이 들어옵니다. 원본 사고 과정은 어떤 설정으로도 반환되지 않습니다.
1
2
3
4
5
6
7
8
9
message = client.messages.create(
model="claude-opus-5",
max_tokens=2048,
thinking={"type": "adaptive", "display": "summarized"},
messages=[{"role": "user", "content": "17 * 24를 계산해줘."}],
)
for block in message.content:
if block.type == "thinking":
print("[사고 요약]", block.thinking)
thinking을 어떻게 조절하는지는 5편에서 다룹니다. 지금은 “content에 텍스트만 오지는 않는다”는 사실만 확인하면 됩니다.
max_tokens 함정
max_tokens는 thinking과 응답 텍스트를 합친 상한입니다. 분류 작업처럼 답이 한 단어인 경우에 예전 감각으로 값을 작게 주면 문제가 생깁니다.
1
2
3
4
5
6
7
message = client.messages.create(
model="claude-opus-5",
max_tokens=16, # 라벨 한 단어면 충분하다고 생각한 값
messages=[{"role": "user", "content": "이 문장의 감정은? 배송이 일주일 걸렸다."}],
)
print(message.stop_reason) # max_tokens
print(text_of(message)) # (빈 문자열)
thinking이 16 토큰을 다 쓰고 끝나서 텍스트가 한 글자도 나오지 않았습니다. 실패는 예외가 아니라 빈 문자열로 나타나므로 조용히 넘어가기 쉽습니다.
stop_reason을 먼저 확인하는 습관이 필요합니다.
| stop_reason | 의미 |
|---|---|
end_turn | 모델이 할 말을 마쳤습니다. 정상 종료입니다 |
max_tokens | 상한에 걸려 잘렸습니다. 값을 올리거나 작업을 쪼갭니다 |
tool_use | 도구 호출을 요청했습니다 (4편) |
refusal | 안전 분류기가 요청을 거절했습니다. content가 비어 있을 수 있습니다 |
권장 기본값은 스트리밍 없이 쓸 때 16000 정도입니다. 상한은 상한일 뿐이고 짧은 답에는 짧게 나오므로, 넉넉히 주는 쪽이 안전합니다. 답을 짧게 만들고 싶다면 max_tokens가 아니라 프롬프트로 지시합니다(2편).
temperature는 어디 갔나
검색하면 나오는 자료 대부분이 “정답형 작업은 temperature를 0에 가깝게” 라고 설명합니다. Claude 5 계열(claude-opus-5, claude-sonnet-5)에서는 이 파라미터가 제거되어 값을 넣으면 400으로 거절됩니다.
1
2
3
4
5
6
client.messages.create(
model="claude-opus-5",
max_tokens=1024,
temperature=0, # 400 invalid_request_error
messages=[...],
)
대신 두 가지를 씁니다.
- effort: 모델이 얼마나 깊이 생각하고 얼마나 많이 움직일지를 정합니다.
low,medium,high,xhigh,max다섯 단계이고 기본값은high입니다 - 프롬프트: 출력의 폭을 좁히는 일은 지시문으로 합니다. “네 카테고리 중 하나만, 다른 말 없이”가 temperature 0보다 확실합니다
1
2
3
4
5
6
7
message = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
output_config={"effort": "low"}, # 짧고 정해진 작업
messages=[{"role": "user", "content": "다음 문의를 배송/환불/상품문의/기타 중 하나로 분류해. 라벨만 출력해.\n\n주문한 지 일주일인데 아직 안 왔어요."}],
)
print(text_of(message)) # 배송
effort는 output_config 안에 들어갑니다. 최상위 인자가 아닙니다.
토큰과 비용 확인
응답의 usage에 이번 호출이 쓴 토큰이 들어 있습니다.
1
2
print(message.usage.input_tokens) # 프롬프트 토큰
print(message.usage.output_tokens) # thinking + 응답 토큰
호출 전에 미리 세려면 count_tokens를 씁니다. OpenAI의 tiktoken은 다른 tokenizer라서 Claude 토큰 수를 15~20% 낮게 잡습니다. 쓰지 않습니다.
1
2
3
4
5
resp = client.messages.count_tokens(
model="claude-opus-5",
messages=[{"role": "user", "content": long_document}],
)
print(resp.input_tokens)
비용은 0편의 단가표를 그대로 곱하면 됩니다. Opus 5 기준으로 입력 1000 토큰과 출력 500 토큰이면 1000/1e6*5 + 500/1e6*25 = 0.0175달러입니다. 이 계산을 매 실습마다 하기는 번거로우므로, 채점 스크립트에 토큰 누적을 넣는 방법을 6편에서 다룹니다.
정리
| 항목 | 확인할 것 |
|---|---|
system | 고정 규칙. 앞쪽에 둘수록 잘 지켜지고 캐시에 유리합니다 |
messages | 매번 바뀌는 입력. 첫 항목은 반드시 user입니다 |
content | block 목록입니다. 텍스트만 뽑는 헬퍼를 두고 씁니다 |
max_tokens | thinking을 포함한 상한입니다. 작게 주면 답이 통째로 사라집니다 |
stop_reason | 응답을 읽기 전에 먼저 봅니다 |
temperature | Claude 5 계열에는 없습니다. effort와 프롬프트로 대신합니다 |
다음 편에서 이 요청의 system과 user를 실제로 다듬어, 같은 입력에서 출력이 어떻게 달라지는지 확인합니다.