포스트

프롬프트 엔지니어링 기초 (1) - First Call: 요청과 응답 뜯어보기

messages 요청의 항목별 의미와 응답의 content block 구조, max_tokens와 thinking의 관계, usage로 토큰과 비용을 확인하는 방법을 다룹니다.

프롬프트 엔지니어링 기초 (1) - First Call: 요청과 응답 뜯어보기

프롬프트 엔지니어링 기초 시리즈의 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: 실제 대화입니다. userassistant가 번갈아 나오며 첫 항목은 반드시 user입니다

systemmessages는 자리만 다른 것이 아닙니다. 모델이 읽는 순서는 toolssystemmessages이고, 앞쪽일수록 잘 지켜지며 캐시에도 유리합니다(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_tokensthinking과 응답 텍스트를 합친 상한입니다. 분류 작업처럼 답이 한 단어인 경우에 예전 감각으로 값을 작게 주면 문제가 생깁니다.

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))   # 배송

effortoutput_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입니다
contentblock 목록입니다. 텍스트만 뽑는 헬퍼를 두고 씁니다
max_tokensthinking을 포함한 상한입니다. 작게 주면 답이 통째로 사라집니다
stop_reason응답을 읽기 전에 먼저 봅니다
temperatureClaude 5 계열에는 없습니다. effort와 프롬프트로 대신합니다

다음 편에서 이 요청의 systemuser를 실제로 다듬어, 같은 입력에서 출력이 어떻게 달라지는지 확인합니다.

다음 글: 프롬프트 엔지니어링 기초 (2) - Instructions: 지시를 구체적으로 쓰는 법

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.