왜 stop 시퀀스가 비용 문제인가
LLM API 요금은 대부분 출력 토큰 기준으로 과금된다. 프롬프트에서 원하는 답을 얻었는데도 모델이 계속 부연 설명, 반복, 예시를 이어 붙이면 그만큼 토큰 비용과 지연 시간이 늘어난다. 특히 구조화된 출력(JSON 한 덩어리, 한 줄 SQL, 분류 라벨)만 필요한 파이프라인에서는 뒷부분이 전부 낭비다.
stop 시퀀스는 이 낭비를 줄이는 가장 단순한 장치다. 특정 문자열이 생성되는 순간 모델을 멈추게 해서, "필요한 만큼만" 생성하도록 강제한다. max_tokens로 자르는 것과 달리 의미 경계에서 끊기므로 출력이 깨지지 않는다.
max_tokens와의 차이
둘 다 출력을 제한하지만 성격이 다르다. max_tokens는 길이 상한(안전장치)이고, stop은 의미 기반 종료 조건이다. 함께 쓰는 것이 정석이다.
| 항목 | max_tokens | stop 시퀀스 |
|---|---|---|
| 종료 기준 | 토큰 개수 | 특정 문자열 매칭 |
| 출력 온전성 | 중간에 잘릴 수 있음 | 경계에서 깔끔히 종료 |
| 주 용도 | 폭주 방지 상한 | 조기 종료·비용 절감 |
기본 적용 예시
대화형 포맷에서 다음 화자 태그가 나오기 전에 멈추게 하는 전형적인 패턴이다. Claude API 기준 예시로, 정지 문자열이 매칭되면 응답의 stop_reason이 stop_sequence로 반환된다.
import anthropic
client = anthropic.Anthropic()
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512, # 폭주 방지 상한
stop_sequences=["\n\nQ:", "###"], # 다음 질문/구분자 직전에서 종료
messages=[
{"role": "user", "content": "다음 질문에 한 문단으로만 답해줘.\n\nQ: stop 시퀀스란?\nA:"}
],
)
print(resp.stop_reason) # "stop_sequence"
print(resp.stop_sequence) # 실제로 매칭된 문자열
print(resp.content[0].text)
여기서 핵심은 stop 문자열이 출력에 포함되지 않고 그 직전에서 잘린다는 점이다. 따라서 후처리에서 태그를 다시 지울 필요가 없다.
구조화 출력에서의 활용
JSON 한 개만 필요한데 모델이 뒤에 설명을 덧붙이는 경우가 흔하다. 닫는 괄호 다음에 나올 법한 마커를 stop으로 지정하거나, 프롬프트에서 종료 마커를 스스로 출력하게 유도한 뒤 그 마커를 stop으로 잡는다.
{
"model": "claude-sonnet-4-6",
"max_tokens": 300,
"stop_sequences": ["```", "\n\n설명:", "END"],
"messages": [
{
"role": "user",
"content": "아래 문장을 분류해 JSON만 출력하고 마지막에 END를 붙여라.\n입력: 배송이 하루 만에 왔어요\n출력 예: {\"label\":\"positive\"}END"
}
]
}
이렇게 하면 라벨 한 개짜리 응답이 수십 토큰 안에서 끝난다. 대량 배치 분류에서는 건당 절감폭이 작아도 전체 비용에 크게 반영된다.
얼마나 절감되는가
절감 효과는 "원래 얼마나 넘치게 생성했는가"에 비례한다. 분류·추출처럼 정답이 짧은 작업일수록 효과가 크고, 긴 서술형 답변에서는 효과가 미미하다. 실측 없이 도입 효과를 단정하지 말고, 로그에서 stop_reason 분포와 평균 출력 토큰을 도입 전후로 비교하는 것이 정확하다.
stop_reason=stop_sequence비율이 높다 → 조기 종료가 실제로 작동 중.stop_reason=max_tokens비율이 높다 → stop이 안 먹거나 상한이 낮음. 재점검 필요.
주의점
stop 시퀀스는 강력하지만 오작동하면 답을 조기에 잘라버린다. 실무에서 자주 겪는 함정은 다음과 같다.
- 정상 출력에 등장하는 문자열을 stop으로 지정: 예를 들어 코드 생성인데
```를 stop으로 쓰면 코드 블록이 시작되자마자 끊긴다. - 과도한 개수 지정: 여러 문자열을 등록할수록 예기치 않은 조기 종료 위험이 커진다. 꼭 필요한 것만.
- 공백·개행 민감성:
"Q:"와"\n\nQ:"는 매칭 위치가 다르다. 프롬프트의 실제 포맷과 정확히 맞춰야 한다. - 스트리밍 처리: 스트리밍 중 stop으로 종료되면 마지막 청크 뒤에 종료 이벤트가 오므로, 클라이언트가 이를 정상 완료로 처리하는지 확인한다.
정리하면, stop 시퀀스는 max_tokens 상한과 함께 쓰고, 출력 포맷과 절대 겹치지 않는 경계 문자열을 최소한으로 지정하며, 도입 후 stop_reason 분포로 검증하는 것이 안전한 운영 방식이다.