토큰과 컨텍스트 윈도우, 비결정성 같은 기초는 알고 있다고 생각하다 시험에서 틀리기 쉬운 영역입니다. 이 수업에서는 그 개념들을 코드에서 실제로 마주치는 형태로 다룹니다.
토큰
모델은 글자가 아니라 토큰 단위로 읽고 씁니다. 토큰은 단어보다 작을 수도 크기도 하고, 흔한 단어는 하나로, 드문 단어나 한국어는 여러 조각으로 나뉘는 경향이 있습니다.
실무에서 중요한 결과는 셋입니다.
- 글자 수와 토큰 수는 비례하지 않습니다. 언어와 내용에 따라 비율이 달라지므로, 문자열 길이로 비용을 추정하면 어긋납니다.
- 입력과 출력 토큰이 따로 계산됩니다. 응답의 사용량 정보에 나뉘어 옵니다.
- 한도는 둘입니다. 하나는 요청 전체가 들어갈 수 있는 컨텍스트 윈도우, 다른 하나는 이번 응답의 최대 출력 길이입니다.
resp = client.messages.create(model=MODEL, max_tokens=1024, messages=messages)
print(resp.usage.input_tokens, resp.usage.output_tokens)
max_tokens를 컨텍스트 윈도우와 혼동하는 것이 흔한 오해입니다. max_tokens는 이번 응답을 얼마나 길게 쓸 수 있는가이고, 컨텍스트 윈도우는 입력과 출력을 합쳐 한 요청에 들어갈 수 있는 총량입니다. 그래서 입력이 길수록 쓸 수 있는 출력 자리가 줄어듭니다.
컨텍스트 윈도우
컨텍스트 윈도우는 입력과 출력이 함께 쓰는 공간입니다. 대화가 길어지면 이력 전체가 매 요청에 다시 실려 가므로, 턴이 늘수록 입력 토큰이 계속 커집니다.
이 성질에서 두 가지가 따라옵니다.
비용이 대화 길이의 제곱에 가깝게 늘어납니다. 10턴짜리 대화는 10번의 요청이지만, 각 요청이 그때까지의 이력을 모두 담습니다.
한도에 가까워지면 자리를 만들어야 합니다. 오래된 부분을 요약하거나, 큰 도구 결과를 줄이거나, 새 대화로 넘깁니다.
# 이력이 커지는 것을 눈으로 확인하는 가장 쉬운 방법
for turn in range(5):
resp = client.messages.create(model=MODEL, max_tokens=512, messages=messages)
print(turn, resp.usage.input_tokens) # 계속 증가한다
messages.append({"role": "assistant", "content": resp.content})
messages.append({"role": "user", "content": next_question()})
윈도우가 크면 편하지만 공짜가 아닙니다. 넣을 수 있다고 다 넣으면 비용이 오르고, 관련 없는 내용이 많으면 오히려 답이 흐려집니다. 넣을 수 있는 양과 넣어야 하는 양은 다릅니다.
비결정성
같은 입력에 같은 출력이 보장되지 않습니다. 모델은 다음 토큰의 확률 분포에서 하나를 고르며 진행하고, 그 선택에 무작위성이 들어갑니다.
온도를 낮추면 분포에서 가장 확률이 높은 쪽으로 쏠려 결과가 안정됩니다. 다만 0으로 두어도 완전히 같은 출력이 보장되지는 않습니다. 재현이 필요한 작업이라면 출력 자체를 저장해 두는 편이 확실합니다.
이 성질이 개발에 미치는 영향이 큽니다.
- 단위 테스트에서 출력 문자열을 그대로 비교하면 깨집니다. 형식과 필수 요소를 검증하는 쪽으로 짜야 합니다.
- 한 번 잘 나왔다고 고쳐진 것이 아닙니다. 여러 번 돌려 실패율을 봐야 합니다.
- 사용자가 "아까는 됐는데"라고 할 때, 코드가 바뀌지 않았어도 사실일 수 있습니다.
# 나쁜 테스트
assert result == "요약: 매출이 12% 증가했습니다."
# 나은 테스트
assert "12%" in result and len(result) < 200
assert schema_valid(result)
사고 모드와 프롬프트 기법
모델에는 더 오래 생각하게 하는 설정이 있습니다. 어려운 추론이 필요한 작업에서 정확도가 오르지만, 토큰과 지연이 함께 늘어납니다. 단순 분류나 형식 변환처럼 추론이 거의 없는 작업에 켜면 비용만 오릅니다.
프롬프트 쪽 기본 기법도 함께 정리해 둡니다.
- 제로샷 — 예시 없이 지시만 줍니다. 작업이 명확할 때 충분합니다.
- 원샷·퓨샷 — 예시를 한 개 또는 여러 개 줍니다. 형식이나 판단 기준을 말로 설명하기 어려울 때 효과가 큽니다.
퓨샷에서 주의할 점이 있습니다. 예시가 편향을 만듭니다. 카테고리 다섯 개짜리 분류에서 예시 네 개가 같은 카테고리면 모델이 그쪽으로 기울어집니다. 예시는 분포를 대표하도록 고르고, 순서도 섞는 편이 낫습니다.
예시를 늘릴수록 좋아지는 것도 아닙니다. 어느 지점을 넘으면 토큰만 늘고 개선이 멈춥니다. 평가 세트로 확인하며 늘리는 것이 맞습니다.
시험 함정
- max_tokens를 컨텍스트 윈도우와 같은 것으로 보는 선택지 — 하나는 이번 출력 한도, 다른 하나는 입출력 총량입니다.
- 글자 수로 토큰과 비용을 추정하는 접근 — 언어와 내용에 따라 비율이 달라집니다.
- 대화가 길어져도 요청당 입력 토큰은 일정하다는 서술 — 이력 전체가 매번 다시 실려 갑니다.
- 온도를 0으로 두면 완전히 같은 출력이 보장된다는 단정 — 재현이 필요하면 출력을 저장해야 합니다.
- 출력 문자열을 그대로 비교하는 테스트 — 형식과 필수 요소를 검증하는 쪽이 맞습니다.
- 퓨샷 예시는 많을수록 좋다는 서술 — 편향이 생기고 어느 지점부터는 토큰만 늘어납니다.
실습 시나리오
실제 시험과 같은 형식의 시나리오 문제입니다.
대화형 도우미의 요청당 비용이 세션이 길어질수록 계속 오릅니다. 프롬프트와 모델은 그대로입니다. 원인으로 가장 적절한 것은 무엇입니까?
빌드 연습 · 토큰 사용량을 직접 관찰하기
약 30분1.언어별 토큰 비교
같은 뜻의 한국어와 영어 문단을 각각 보내 입력 토큰을 비교합니다.
기대 결과 · 글자 수와 토큰 수가 비례하지 않는 것이 확인됩니다.
2.이력 누적 관찰
5턴 대화를 돌리며 턴마다 입력 토큰을 기록합니다.
기대 결과 · 증가 곡선이 표로 남습니다.
3.잘림 재현
max_tokens를 작게 주고 stop_reason을 확인합니다.
기대 결과 · max_tokens 값이 관찰되고 내용이 끊깁니다.
4.변동성 측정
같은 입력을 10회 보내 출력이 얼마나 달라지는지 봅니다.
기대 결과 · 완전히 동일하지 않은 사례가 관찰됩니다.
5.퓨샷 편향 확인
한 카테고리에 치우친 예시로 분류를 돌려 결과 분포를 봅니다.
기대 결과 · 예시가 많은 쪽으로 기우는 경향이 관찰됩니다.