-
vLLM reasoning_parser — <think> 블록을 정규식 말고 구조로 받는 법IT 2026. 5. 6. 22:30
들어가며 — 정규식 5개로 버티던 코드
Qwen3.6은 응답 중에 자기 생각을
<think>...</think>태그로 감싸 출력한다. 이 안에는 모델이 답을 만들기 전에 한 추론 과정이 들어 있고, 사용자에게 보여줄 본문은 그 다음에 나온다.문제는 이 thinking 블록이 본문에 섞여 그대로 도착한다는 것이다. RAG 인덱싱 코드, 평가 코드, 채팅 본문 출력 — 모두 이 블록을 본문에서 깎아내야 한다. 그래서 정규식 안전망 코드가 약 40줄 붙어 있었다.
# 닫히지 않은 </think> 처리 # 비ASCII garbage 잔해 제거 # 중첩 태그 방어 # 부분 출력 케이스 분기 # ... 5개 정도의 정규식 ...vLLM에 옵션 한 줄을 더하면 이 코드가 통째로 사라진다.
--reasoning-parser qwen3이 글은 그 옵션이 무엇을 하는지, 왜 정규식보다 나은지, 그리고 thinking 모델 응답을 다루는 더 깔끔한 방식이 무엇인지 풀어쓴다.
1. thinking 모델의 출력은 어떻게 생겼는가
두 부분이 한 문자열에 섞여 도착한다. 본문만 쓰려면
<think>...</think>를 잘라내야 한다.그런데 잘라내는 게 단순한 일이 아니다. 모델 출력은 절대 깨끗하지 않다. 일어나는 사고들:
- 토큰 한도가 다 차서 닫히지 않은
<think>가 그대로 도착 - 중간에 비ASCII garbage가 끼어들어 정규식이 헛돔
- 스트리밍에서 부분 청크가 도착해 태그가 토큰 경계에 걸침 (
<thi|nk>) - 다른 모델로 바꿨더니 태그 형식이 미묘하게 다름 (
<|thinking|>등)
2. 정규식 안전망의 비용
정규식 안전망의 진짜 비용은 줄 수가 아니다. 모델 출력 형식이 살짝만 바뀌어도 깨진다는 점이다. Qwen3 → Qwen3.5 업그레이드, 또는 Qwen → Llama 교체 시 모든 정규식을 다시 검증해야 한다. 매 호출마다 머릿속으로 "이게 깨지면 어쩌지"가 따라다니는 인지 부하도 있다.
3. reasoning_parser는 무엇을 하는가
vLLM에
--reasoning-parser qwen3를 켜면, 서버가 응답을 OpenAI 호환 JSON으로 묶기 전에 thinking 블록을 미리 분리한다.본문은 깨끗하게 비어 있는 채로 도착한다. 클라이언트는
response.choices[0].message.content를 그대로 쓰면 끝. thinking이 필요하면reasoning_content를 별도로 본다.
4. 정규식 vs 파서 — 무엇이 다른가
관점 정규식 안전망 reasoning_parser 처리 위치 호출자 측 (각자) 서버 측 (한 곳) 스트리밍 상태 머신 직접 구현 서버가 자동 처리 모델 변경 시 각 호출자의 정규식 재검증 서버 옵션만 교체 실패 모드 본문에 think 잔해 노출 JSON 필드 누락 (명확) 코드량 호출자당 수십~수백 줄 0줄 인지 부하 매 호출마다 "이게 깨지면?" 없음 핵심은 "문제를 옳은 곳에서 푼다"는 점이다. 모델이 만든 형식의 파싱은 모델을 운영하는 서버가 가장 잘 안다. 호출자 6곳이 각자 다른 정규식으로 같은 문제를 푸는 건, 같은 버그를 6번 반복할 위험이다.
5. 호출자 코드는 이렇게 단순해진다
호출자 6곳에서 약 150줄의 정규식 코드가 사라졌다. "줄 수 감소" 자체보다, 모델 업그레이드 때마다 정규식을 재검증할 일이 사라진 게 더 크다.
정리
thinking 모델의 응답을 다루는 두 가지 방식.
- 정규식 안전망 (전통적): 호출자마다 패턴 매칭으로 thinking 블록을 깎는다. 단순하지만 모델·형식 변화에 fragile하다.
- reasoning_parser (구조적): 서버가 thinking을 미리 분리해 OpenAI JSON의
reasoning_content필드로 따로 보낸다. 호출자는 그냥content만 보면 끝.
한 줄로 줄이면, "형식의 파싱은 그 형식을 가장 잘 아는 곳에서 한 번만 한다"이다. 호출자 6곳에서 같은 일을 6번 다른 방식으로 처리하던 코드가, 서버 옵션 한 줄로 사라진다. 모델 업그레이드 시에도 호출자 코드는 그대로 살아남는다.
thinking 모델을 OpenAI-compat으로 받는 환경이라면,
--reasoning-parser는 거의 비용 없는 즉시 적용 옵션이다.
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
로컬 챗봇 시리즈 #1 — 메시지 편집은 왜 그렇게 단순해야 하나: 컨텍스트 엔지니어링 관점에서 (0) 2026.05.08 Ralph Loop — bash while true + LLM CLI가 만든 어이없게 강력한 에이전트 패턴 (0) 2026.05.07 GPU 스케줄러를 Ollama warmup에서 vLLM 컨테이너로 옮긴 과정 — 시작·종료 시퀀스를 다시 짜다 (0) 2026.05.06 RAG 청크 맥락에서 thinking을 꺼야 하는 이유 — enable_thinking=False가 필요한 순간 (0) 2026.05.06 OpenAI-compat 표준화로 어댑터 100줄 들어내기 — passthrough가 가져온 코드 청결도 (0) 2026.05.06 KV cache FP8로 동시 요청 76배 수용하기 — LLM 메모리의 숨은 주범 정리 (0) 2026.05.06 Ollama에서 vLLM으로 백엔드를 바꿨더니 throughput이 148% 올랐다 — 같은 모델, 다른 엔진 (0) 2026.05.06 Gemma 4에서 Qwen 3.6으로 갈아탔다 — 두 모델을 모든 지표로 비교한 기록 (0) 2026.05.06 Fused 커널은 왜 3~4배 빠른가 — GPU 메모리 계층과 Marlin의 비밀 (0) 2026.05.05 Q4 양자화는 GPU 안에서 어떻게 동작하나 — Blackwell FP8 텐서코어와의 만남 (0) 2026.05.05 - 토큰 한도가 다 차서 닫히지 않은