배달 앱에 이런 리뷰가 올라왔습니다. LLM에게 이 리뷰를 긍정·부정·섞임 가운데 하나로 나눠 달라고 시켜 보겠습니다. LLM에게 보내는 글을 프롬프트라고 합니다.
맛은 최고인데 배달이 한 시간 걸렸어요
그림에서 프롬프트에 넣는 예시를 0개, 1개, 3개로 바꿔 보세요. 모델은 하나도 바뀌지 않는데 답의 모양이 달라집니다.
답과 확률은 설명을 위해 만든 예시 값입니다. 예시가 하나면 형식을 반쯤만 따라 하고 셋이면 한 단어로 몰립니다. 가중치는 모델이 학습하며 정해 둔 수많은 숫자인데, 예시를 넣어도 하나도 바뀌지 않습니다.
LLM(Large Language Model)은 아주 많은 글을 읽으면서 다음에 올 말을 맞히도록 학습한 프로그램입니다. 글은 토큰 단위로 읽고 씁니다. 토큰은 단어보다 조금 작은 글 조각입니다.
반 친구들이 한 문장씩 이어 쓰는 릴레이 소설을 떠올리면 비슷합니다. 내 차례가 오면 앞 내용을 다 읽고 그 흐름에 맞는 다음 문장을 씁니다. 다만 LLM은 문장 대신 토큰을 하나씩 고릅니다. 앞에 놓인 글 말고는 아무것도 기억하지 않는다는 점도 다릅니다.
messages = [ {…developer…}, {…user…} ]
역할 표시는 설명을 위해 단순하게 그렸습니다. 실제 표시는 모델마다 다르고 공개하지 않는 곳도 많습니다.
역할도, 예시도, 지난 대화도 모두 이 한 줄 글의 앞부분입니다. 프롬프트를 바꾸면 모델이 이어 쓸 글이 바뀌고 다음에 올 말의 확률도 따라 바뀝니다.
프로그램이 LLM을 쓸 때는 API로 부릅니다. API는 다른 프로그램에 일을 시키는 정해진 주문 양식입니다. 답은 JSON이라는 형식으로 돌아오는데, 이름표가 붙은 칸 안에 또 칸이 들어 있는 구조입니다.
택배 상자를 생각하면 쉽습니다. 겉에는 송장(번호·보낸 곳·시각)이 붙어 있고 안에는 물건과 영수증이 같이 들어 있습니다. 응답도 겉에 id·model이, 안에 답이 든 choices가, 옆에 사용량 영수증 usage가 있습니다.
상자를 눌러 보세요. 그 칸까지 들어가는 Python 경로가 아래에 나옵니다. 다만 택배와 달리 내용물은 모델이 방금 만들었습니다. 같은 요청을 다시 보내도 다른 답이 들어 있을 수 있습니다.
모델이 쓴 답 글자입니다. 대부분의 프로그램은 이 한 칸만 꺼내 씁니다.
값은 아래 코드를 돌렸을 때 나오는 모양의 예시입니다. 실제 응답에는 칸이 더 있는데 여기서는 자주 보는 칸만 남겼습니다.
import os
from openai import OpenAI
# 1 API 키는 코드 밖 환경 변수에서
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
TASK = "리뷰를 긍정, 부정, 섞임 중 하나로 분류해."
REVIEW = "맛은 최고인데 배달이 한 시간 걸렸어요"
response = client.chat.completions.create(
model="gpt-5.5",
messages=[
# 2 개발자의 지시
{"role": "developer", "content": TASK},
# 3 분류할 리뷰
{"role": "user", "content": REVIEW},
],
)
# 4 응답 상자 전체를 펼쳐 보기
print(response.model_dump_json(indent=2, exclude_none=True))
# 5 답 글자만 꺼내기
print(response.choices[0].message.content)출력은 예시 답입니다. 같은 코드라도 답 문장과 토큰 수는 그때그때 달라집니다. 값이 0인 칸 일부는 …로 줄였습니다.
system을 썼습니다. 왜 이름이 바뀌었는지는 Ⅱ장에서 봅니다.model_dump_json은 SDK가 만든 응답 객체를 JSON 글자로 펼쳐 줍니다. exclude_none=True는 비어 있는 칸을 빼서 짧게 보여 줍니다. 처음 API를 배울 때 한 번은 꼭 펼쳐 보는 게 좋습니다.choices가 목록이라 [0]으로 첫 번째를 고릅니다. Python 목록은 0번부터 셉니다.여기서 쓴 SDK는 OpenAI가 만든 openai 패키지(3.31)입니다. 주문 양식을 코드로 채우기 쉽게 만든 도구 모음입니다. usage의 completion_tokens 118개 가운데 64개가 reasoning_tokens인 점이 눈에 띕니다. 이 숫자가 다음 실험의 주인공입니다.
finish_reason은 모델이 왜 쓰기를 멈췄는지 알려 주는 칸입니다. "stop"이면 스스로 끝 표시를 골라 다 쓴 경우입니다. "length"면 정해 둔 토큰 상한에 걸려 중간에 끊긴 것입니다.
요즘 모델은 대부분 추론 모델입니다. 답을 쓰기 전에 화면에 보이지 않는 생각을 먼저 토큰으로 써 내려가는 모델입니다. 그 생각 토큰도 상한에 함께 셉니다. 아래 슬라이더로 상한 max_completion_tokens를 줄여 보세요.
토큰 수는 위 터미널 출력의 예시 값(생각 64 + 답 54 = 118)을 따랐습니다. 실제로 생각에 쓰는 양은 질문과 모델에 따라 크게 달라집니다.
상한을 64 아래로 내리면 생각만 하다가 끊깁니다. 이때 content는 빈 글자인데 오류는 나지 않습니다. OpenAI 문서에 따르면 생각 토큰은 화면에 나오지 않지만 요금은 답 토큰과 똑같이 매겨집니다. 보이는 답 없이 요금만 나갑니다.
그래서 답을 쓰기 전에 finish_reason부터 확인하는 습관이 좋습니다. OpenAI는 추론 모델을 처음 써 볼 때 생각과 답에 쓸 몫으로 최소 25,000토큰을 남겨 두라고 권합니다. 상한은 비용을 막는 안전장치이지 답 길이를 맞추는 도구가 아닙니다.
max_tokens는 예전 이름입니다. SDK 설명에는 max_completion_tokens로 바뀌었고 추론 모델과는 맞지 않는다고 적혀 있습니다. 새 이름은 "보이는 답 토큰과 추론 토큰을 합친 상한"이라는 뜻을 분명히 합니다.
메시지의 role은 세 가지를 주로 씁니다. developer는 프로그램을 만든 개발자의 지시, user는 사용자의 말, assistant는 모델이 앞서 한 말입니다.
역할 사이에는 순서가 있습니다. 편의점 아르바이트를 떠올려 보세요. 본사 규정이 가장 먼저이고 그다음이 점장 지시, 마지막이 손님 부탁입니다. 손님이 "규정은 됐고 이거 해 줘요"라고 해도 점장 지시를 먼저 따릅니다.
OpenAI가 모델의 행동 원칙을 적어 둔 Model Spec(2026-08-18판)도 비슷한 순서를 둡니다. OpenAI의 규칙이 맨 위, 그 아래 개발자, 그 아래 사용자입니다. 그림에서 사용자의 말을 바꿔 보세요.
두 지시가 부딪치지 않습니다. 모델은 사용자의 말을 개발자의 지시대로 처리합니다.
답 문장은 설명을 위한 예시입니다.
순서가 있다고 해서 절대 규칙은 아닙니다. 아르바이트생은 규정을 외우고 있지만 모델은 그때그때 글을 읽고 판단합니다. 그럴듯하게 꾸민 말에 넘어가 개발자의 지시를 어기기도 합니다. 이런 공격을 프롬프트 인젝션이라고 합니다.
그래서 꼭 지켜야 하는 규칙은 프롬프트에만 맡기지 않습니다. 모델의 답을 코드에서 한 번 더 검사하거나, 위험한 일은 아예 할 수 없게 막아 둡니다.
OpenAI 문서는 o1 모델부터 developer 메시지가 예전 system 메시지를 대신한다고 적고 있습니다. 지금 Model Spec에서 system은 OpenAI 쪽 규칙을 전하는 자리입니다. 개발자는 developer를 쓰면 됩니다.
다른 회사 API나 내 컴퓨터에 띄운 모델 서버는 아직 system만 받는 곳이 많습니다. 모델을 바꿔 가며 쓰는 코드라면 그쪽 문서를 먼저 확인합니다.
아닙니다. 메시지 목록은 내 코드가 만드는 것이라 assistant 메시지도 직접 써 넣을 수 있습니다. 모델은 그 말을 자기가 앞서 한 말로 읽습니다. Ⅲ장의 "예시를 대화처럼 넣는 방법"이 이 성질을 씁니다.
영화 리뷰가 긍정인지 부정인지 맞히는 SST-2라는 문제 묶음에서 GPT-3 두 크기의 정확도입니다(Zhao 외, 2021, 표 1). 175B는 가중치가 1,750억 개, 2.7B는 27억 개라는 뜻입니다. 예시 묶음을 여러 번 바꿔 가며 잰 평균값입니다.
여기서 샷은 예시 한 개를 말합니다. 예시 없이 지시만 주면 제로샷, 하나 주면 원샷, 여러 개 주면 퓨샷입니다. 2020년 GPT-3 논문이 이 이름을 널리 퍼뜨렸습니다.
시험지 맨 위의 "보기" 문제와 비슷합니다. 풀이 하나를 보여 주면 나머지 문제를 같은 형식으로 풉니다. 다만 학생은 보기에서 배운 걸 다음 시험까지 기억하지만 모델은 이번 요청이 끝나면 예시를 잊습니다.
모델 안의 숫자인 가중치는 하나도 바뀌지 않습니다. 논문도 세 방식 모두 가중치를 고치지 않는다고 적었습니다. 프롬프트 안에서만 일어나는 이 배움을 in-context learning이라고 부릅니다. 모델에게 주어진 글, 곧 context 안에서 배운다는 뜻입니다.
그래프를 보면 예시가 늘 도움이 되지도 않습니다. 큰 모델은 예시 하나에 71.6%에서 93.3%로 뛰었습니다. 작은 모델은 8개를 주자 54.0%로, 찍어서 맞히는 수준까지 내려갔습니다.
예시의 정답을 일부러 틀리게 적으면 어떻게 될까요? "긍정" 리뷰에 "부정"이라고 적어 보여 주는 식입니다. 아래에서 먼저 예상을 골라 보세요.
"리뷰 → 답" 형식 자체를 없애면(리뷰만, 또는 답만 나열) 예시를 아예 안 준 것과 비슷하거나 더 나빠졌습니다.
%p(퍼센트포인트)는 정확도가 몇 칸 내려갔는지입니다. 80%가 75%가 되면 5%p입니다. 막대의 옅은 부분부터 진한 끝까지가 연구에서 나온 하락 폭의 범위입니다. Min 외(2022)가 모델 12개, 데이터 묶음 26개에서 예시 16개로 쟀습니다. 정답을 무작위로 바꿨을 때 분류 문제의 평균 하락은 2.6%p였습니다.
정답을 엉터리로 바꿔도 거의 그대로였습니다. 반대로 리뷰 자리에 엉뚱한 글을 넣거나 답 자리에 아무 단어나 넣으면 크게 떨어졌습니다. 예시는 "어떤 글이 들어오고 답은 어떤 단어 중 하나로, 어떤 모양으로 쓰는가"를 보여 주는 역할이 큽니다.
히어로에서 예시를 넣자 긴 문장 대신 "섞임" 한 단어가 나온 것도 같은 이유입니다. 그렇다고 정답이 아무래도 좋다는 뜻은 아닙니다. 같은 연구에서도 어떤 모델·데이터에서는 14%p 가까이 떨어졌습니다. 예시의 정답은 맞게 적는 게 기본입니다.
Zhao 외(2021)는 같은 예시 4개를 순서만 바꿔 넣었습니다. 작은 GPT-3(2.7B)의 정확도가 54.3%에서 93.4%까지 오갔습니다. 찍는 수준과 거의 최고 수준이 순서 하나로 갈렸습니다.
원인으로 꼽은 쏠림은 세 가지입니다. 모델은 예시에 많이 나온 답 쪽으로 기웁니다. 맨 끝에 가까운 예시의 답도 따라갑니다. 평소에 흔히 보던 단어를 더 고르기도 합니다.
그림에서 순서를 바꿔 보세요. 새 리뷰는 그대로인데 답이 기우는 쪽이 달라집니다. 이렇게 한쪽으로 쏠린 만큼 틀리는 리뷰가 늘어 정확도가 크게 흔들렸습니다. 실제로는 답이 골고루 섞이게 예시를 고르고 순서를 몇 가지로 바꿔 시험해 봅니다.
긍정·부정 예시를 2개씩 같은 수로 넣고 순서만 바꿨습니다. 막대는 세 쏠림 가운데 "끝에 가까운 예시를 따라가는 쏠림" 하나만 단순하게 그린 예시 값입니다. 실제 크기는 모델과 문제에 따라 다릅니다.
첫 번째는 developer 메시지 안에 예시를 태그로 묶어 넣는 방법입니다. 태그는 <example>처럼 꺾쇠로 감싼 표시로, 어디부터 어디까지가 예시인지 알려 줍니다. OpenAI와 Anthropic 문서가 모두 이 방법을 권합니다.
두 번째는 예시를 지난 대화처럼 넣는 방법입니다. user 메시지에 예시 리뷰를, assistant 메시지에 정답을 직접 써 넣습니다. 모델은 자기가 앞서 그렇게 답했다고 읽고 같은 모양으로 이어 갑니다.
<examples> <example>리뷰: 포장이 꼼꼼하고… 답: 긍정</example> <example>리뷰: 국물이 다 새서… 답: 부정</example> <example>리뷰: 양은 많은데… 답: 섞임</example> </examples>user맛은 최고인데 배달이 한 시간 걸렸어요
↑ 모델이 읽는 한 줄 글로 바꾸면 둘 다 "예시 세 개 다음에 새 리뷰"라는 같은 흐름입니다
# ... client, TASK, REVIEW 는 first_call.py 와 같다
# 1 예시 목록: (리뷰, 정답)
EXAMPLES = [
("포장이 꼼꼼하고 아직 따뜻했어요", "긍정"),
("국물이 다 새서 왔어요", "부정"),
("양은 많은데 너무 짜요", "섞임"),
]
# 2 방법 A: 지시 안에 예시를 태그로 묶어 넣기
def in_developer(review):
shots = "\n".join(
f"<example>\n리뷰: {r}\n답: {a}\n</example>"
for r, a in EXAMPLES
)
content = f"{TASK}\n\n<examples>\n{shots}\n</examples>"
return [
{"role": "developer", "content": content},
{"role": "user", "content": review},
]
# 3 방법 B: 예시를 지난 대화처럼 user·assistant 쌍으로
def as_turns(review):
messages = [{"role": "developer", "content": TASK}]
for r, a in EXAMPLES:
messages.append({"role": "user", "content": r})
messages.append({"role": "assistant", "content": a})
messages.append({"role": "user", "content": review})
return messages
for build in (in_developer, as_turns):
messages = build(REVIEW)
response = client.chat.completions.create(
model="gpt-5.5", messages=messages
)
answer = response.choices[0].message.content
# 4 함수 이름, 보낸 메시지 수, 답
print(build.__name__, len(messages), answer)출력은 예시 답입니다. 제로샷 때 긴 문장이던 답이 두 방법 모두 한 단어로 바뀐 것만 보면 됩니다.
예시는 요청할 때마다 다시 보내야 합니다. API는 앞 요청을 기억하지 않기 때문입니다(AI Agent 시리즈 첫 글에서 그림으로 다뤘습니다). 진짜 대화를 이어 가는 멀티턴도 같은 원리라, 예시 뒤에 지난 대화를 차례로 붙여 보냅니다.
입력 토큰으로 매번 셉니다. 다만 앞부분이 매번 똑같으면 OpenAI가 그 부분을 저장해 두고 다시 쓰는 프롬프트 캐싱이 적용될 수 있습니다. 응답의 usage.prompt_tokens_details.cached_tokens에 그 수가 찍힙니다. 그래서 바뀌지 않는 지시와 예시는 앞에, 바뀌는 리뷰는 뒤에 둡니다.
OpenAI는 새 프로젝트에 Responses API를 권합니다. 이 글에서 쓴 Chat Completions는 SDK 설명에 "계속 지원한다"고 적혀 있어 그대로 써도 됩니다. 다른 회사 모델과 로컬 모델 서버도 대부분 Chat Completions 양식을 받습니다.
Responses API는 같은 일을 다른 칸 이름으로 합니다. developer 지시는 instructions에, 사용자 입력은 input에 넣고 답 글자는 output_text로 꺼냅니다.
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
response = client.responses.create(
model="gpt-5.5",
# 1 developer 메시지 자리
instructions="리뷰를 긍정, 부정, 섞임 중 하나로 분류해.",
# 2 사용자 입력
input="맛은 최고인데 배달이 한 시간 걸렸어요",
)
# 3 글만 모아 꺼내기
print(response.output_text)instructions는 developer 메시지를 맨 앞에 끼워 넣는 칸입니다. 지시와 입력이 칸으로 나뉘어 코드가 짧아집니다.input에는 글 한 줄을 그대로 넣어도 되고 메시지 목록을 넣어도 됩니다. 퓨샷 방법 B는 목록으로 넣습니다.output_text는 응답 안의 글을 모아 주는 편의 기능입니다. 응답의 output 목록에는 생각 요약이나 도구 호출이 섞일 수 있어서 첫 칸이 늘 답이라고 가정하면 안 된다고 문서에 적혀 있습니다.추론 모델에서는 퓨샷을 쓰는 순서도 바뀌었습니다. OpenAI 문서는 추론 모델이 예시 없이도 좋은 답을 내는 경우가 많으니 제로샷부터 써 보고 필요할 때 예시를 더하라고 권합니다. 예시를 넣을 때는 지시와 꼭 맞게 고르라고 덧붙입니다.
temperature처럼 답의 들쭉날쭉함을 정하는 옵션도 조건이 붙었습니다. 추론 모델은 생각의 양을 정하는 reasoning_effort가 none이 아니면 이 옵션을 빼야 한다고 OpenAI 문서는 적고 있습니다. 강의 자료에 temperature가 있다면 모델과 함께 확인해야 합니다.
가중치는 그대로이고 다음 요청에는 남지 않습니다. 그래서 프롬프트를 고칠 때는 아래 순서로 점검합니다.
finish_reason과 usage까지 확인했는가