LLM 앱 개발 실전 #2 메시지와 파라미터 이해하기

6 분 소요

1편에서 첫 호출을 해봤습니다. 그때는 messages에 사용자 메시지 하나만 넣고 넘어갔는데, 이번에는 그 messages의 구조와 호출에 함께 넘기는 주요 파라미터를 제대로 살펴봅니다. 이 점을 알아야 Claude에게 맥락과 지시를 정확히 전달할 수 있습니다.

대화는 메시지의 목록이다 #

messages는 이름 그대로 메시지의 목록입니다. 각 메시지는 누가 말했는지를 나타내는 role과 내용인 content로 이루어집니다. role에는 두 가지가 있습니다.

  • user — 사용자가 한 말
  • assistant — Claude가 한 답변

1편에서는 user 하나만 넣었습니다. 그런데 대화를 이어가려면 이전에 주고받은 내용을 알려줘야 합니다. 여기서 LLM API의 중요한 성질이 하나 있습니다. API는 이전 대화를 기억하지 않습니다. 매 호출은 독립적이고, 서버는 지난번에 무슨 말을 했는지 저장해 두지 않습니다. 그래서 대화를 이어가려면 지금까지의 내용을 매번 통째로 다시 보내야 합니다.

multi_turn.py
import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "내 이름은 민수야."},
        {"role": "assistant", "content": "안녕하세요 민수님, 반갑습니다."},
        {"role": "user", "content": "내 이름이 뭐라고 했지?"},
    ],
)

for block in response.content:
    if block.type == "text":
        print(block.text)

assistant 메시지를 직접 목록에 넣어 이전 답변을 다시 들려주는 모습에 주목하세요. Claude는 이 messages 전체를 읽고 답하므로, “민수"라는 이름을 정확히 기억해 답합니다. 만약 마지막 user 메시지만 보냈다면 Claude는 이름을 알 방법이 없습니다.

그래서 챗봇을 만들 때는 주고받은 메시지를 차곡차곡 목록에 쌓아 두고, 호출할 때마다 그 전체를 보냅니다. 대화가 길어지면 이 목록도 길어지고, 길어진 만큼 토큰 비용이 올라갑니다. 이걸 어떻게 관리하는지는 #9에서 다루겠습니다.

대화를 코드로 이어가기 #

위 예제는 messages를 손으로 적어 넣었습니다. 실제 챗봇에서는 대화가 오갈 때마다 이 목록에 메시지를 더해 갑니다. 패턴은 간단합니다. 사용자가 말하면 user 메시지를 목록에 더하고, 호출해서 받은 답변을 assistant 메시지로 다시 목록에 더합니다.

conversation_loop.py
import anthropic

client = anthropic.Anthropic()
messages = []

def chat(user_input: str) -> str:
    messages.append({"role": "user", "content": user_input})

    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        messages=messages,
    )
    answer = next(b.text for b in response.content if b.type == "text")

    messages.append({"role": "assistant", "content": answer})
    return answer

print(chat("내 이름은 민수야."))
print(chat("내 이름이 뭐라고 했지?"))  # "민수" 를 기억해 답한다

핵심은 받은 답변(assistant)을 반드시 다시 messages에 더해 두는 부분입니다. 이 한 줄을 빼먹으면 다음 호출 때 Claude는 자기가 방금 한 말을 모릅니다. 사용자 입력만 쌓이고 답변은 쌓이지 않아, 대화가 한쪽만 이어지는 셈이 됩니다.

system 프롬프트로 역할과 규칙 정하기 #

systemmessages와 별개의 파라미터로, Claude에게 역할과 규칙을 미리 일러두는 데 씁니다.

system_prompt.py
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    system="너는 초보자에게 친절한 파이썬 강사야. 답변에는 항상 짧은 예제 코드를 포함해.",
    messages=[
        {"role": "user", "content": "리스트를 정렬하는 법을 알려줘."}
    ],
)

system에 적은 지침은 이후 모든 답변에 영향을 줍니다. user 메시지가 매번 바뀌어도 system의 지침은 그대로 유지됩니다. 위 예제라면 사용자가 무엇을 묻든 Claude는 친절한 강사의 말투로 예제 코드를 곁들여 답합니다.

systemuser의 역할을 구분해 두면 좋습니다.

  • system — 대화 내내 지속되는 역할, 말투, 출력 규칙
  • user — 이번 turn의 구체적인 요청

“너는 파이썬 강사야” 같은 지침을 매 user 메시지마다 반복할 필요가 없다는 뜻입니다. 한 번 system에 적어 두면 됩니다.

max_tokens — 응답 길이의 상한 #

max_tokens는 응답이 최대 몇 토큰까지 생성될지 정하는 상한입니다. 1편에서 봤듯이 이 값을 넘으면 응답이 중간에 잘립니다.

토큰은 단어보다 작은 글자 조각이라고 생각하면 됩니다. 영어는 대략 한 단어가 1〜2토큰이고, 한국어는 글자당 토큰을 더 씁니다. 정확한 환산은 지금 신경 쓰지 않아도 되고, “길수록 토큰이 많다” 정도로 충분합니다.

너무 작게 잡으면 답이 잘립니다. 그렇다고 크게 잡아서 손해를 보지는 않습니다. 실제 생성된 만큼만 과금되고, max_tokens는 어디까지 허용할지의 상한일 뿐입니다. 그래서 보통 넉넉하게 1024나 그 이상을 줍니다.

응답이 잘렸는지는 response.stop_reason으로 확인합니다.

  • end_turn — Claude가 할 말을 마치고 자연스럽게 끝났습니다.
  • max_tokens — 상한에 걸려 중간에 잘렸습니다. 값을 늘려야 합니다.

temperature — 답변의 무작위성 #

temperature는 답변이 얼마나 다양하게 나올지를 조절합니다. 0에 가까우면 매번 비슷하고 안정적인 답이 나오고, 1에 가까우면 더 다양하고 창의적인 답이 나옵니다. 값의 범위는 0~1입니다.

temperature.py
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    temperature=0.0,
    messages=[
        {"role": "user", "content": "버그를 고친 커밋 메시지를 한 줄로 써줘."}
    ],
)

언제 어떤 값을 쓰는지는 작업 성격으로 정합니다.

  • 낮게(0에 가깝게) — 사실 추출, 분류, 코드 생성처럼 일관성과 정확성이 중요할 때
  • 높게(1에 가깝게) — 광고 카피, 브레인스토밍처럼 다양한 표현이 필요할 때

한 가지 오해를 짚어 두겠습니다. temperature=0이라고 해서 매번 글자까지 똑같은 답이 나오는 것은 아닙니다. “더 안정적"이라는 뜻이지 “완전히 똑같다"는 뜻은 아닙니다.

노트
temperature는 Sonnet과 Haiku에서 쓸 수 있습니다. 가장 강력한 등급인 Opus 최신 모델(claude-opus-4-8 등)은 무작위성을 내부에서 알아서 조절하므로 이 파라미터를 받지 않고, 넣어서 호출하면 오류가 납니다. 이 시리즈의 기본 모델인 claude-sonnet-4-6에서는 정상 동작합니다.

흔히 걸려 넘어지는 곳 #

메시지를 다루다 보면 자주 마주치는 문제들입니다.

  • 첫 메시지는 user여야 한다messages의 첫 항목을 assistant로 넣으면 오류가 납니다. 대화는 항상 사용자의 말로 시작합니다.
  • Claude가 방금 한 말을 기억하지 못한다 — 대개 이전 대화를 안 보낸 경우입니다. API는 상태를 저장하지 않으므로, 이어지는 대화에서는 직전까지의 messages를 모두 포함해야 합니다.
  • system 지침이 안 먹힌다 — 역할 지침을 user 메시지 안에 적지 않았는지 확인합니다. 지침은 별도의 system 파라미터로 넣어야 합니다.

마무리 #

이번 글에서는 messages의 구조와 호출에 함께 넘기는 핵심 파라미터를 정리했습니다.

  • 대화는 role을 가진 메시지의 목록이고, API는 상태를 저장하지 않으므로 멀티턴에서는 히스토리를 매번 전부 보냅니다.
  • system은 대화 내내 지속되는 역할과 규칙, user는 이번 요청입니다.
  • max_tokens는 응답 길이의 상한이고, stop_reason으로 잘렸는지 확인합니다.
  • temperature는 답변의 무작위성을 조절합니다. 0은 안정적, 높을수록 다양합니다.

다음 글인 “LLM 앱 개발 실전 #3 스트리밍으로 응답 실시간 출력"에서는 응답을 한 번에 받지 않고 생성되는 대로 화면에 흘려보내는 방법을 다루겠습니다. 긴 답변을 기다리는 체감 시간을 크게 줄여 주는 기법입니다.

X