vLLM Gemma 도구 인자 문자열 변환으로 422 실패¶
한 줄 요약: Gemma로 앱·파이프라인을 만들면 같은 실패가 반복돼 422로 끝나고, 대시보드는
plan_hash불일치로 글로만 답한다. 원인은 세 갈래다. (A) vLLM v0.24+ Gemma 파서가 숫자·참거짓을 문자열로 두는데 타입 없는payload가 이를 그대로 노출, (B) Gemma가 코드 줄바꿈을\n두 글자로 씀, (C) 승인 단계에서 Gemma가 계획을 다시 써서 지문이 어긋남. 원인은 규명했고 수정은 아직이다.
🚨 증상¶
- 어시스턴트에서 Gemma(
gemma-4-31b-it)로 앱·파이프라인·온톨로지를 요청하면 실패한다. Claude는 같은 요청에 성공한다. - 대시보드는 계획 미리보기까지 나오지만 승인 카드 없이 글로만 답한다. 됐다 안 됐다 한다.
schema.fields.N.nullable: Input should be a valid boolean # 갈래 A layout.span must be an integer 1-12 # 갈래 A code item '...' content is not valid Python at line 1, column 42: unexpected character after line continuation character # 갈래 B plan does not match plan_hash # 갈래 C 422 "The assistant repeated a failing tool call." # 공통 끝 - 승인 카드가 뜬 대시보드도 위젯 위치가
{"x": "0", "w": "6"}처럼 전부 문자열이었다.
📍 상황¶
- 2026-10-27 13:53 — 'gemma 어시스턴트 테스트' 결과를 보고 원인을 분석했다(→ 작업 4). 로컬 시험 후 이슈 #3561을 올렸다(→ 작업 6). → 그날 일지
- 2026-11-12 14:57~17:47 — "앱·대시보드만이 아니라 모든 리소스가 되게" 하려고 다시 분석했다(→ 작업 4). 실패 13건 분류(14:57), 개발 클러스터 어시스턴트 로그 확인(16:54), 재난 컬렉션 재테스트(17:36~17:46). 지난번 원인(A)은 그대로이고, 다른 원인 B·C가 더 있었다. → 그날 일지
🔍 원인¶
겉으로는 "Gemma가 못한다" 하나로 보였다. 실제로는 갈래마다 책임이 달랐다.
| 갈래 | 무엇이 깨지나 | 책임 |
|---|---|---|
| A 숫자·참거짓이 문자열 | nullable, precision, span |
vLLM 파서 + 우리 스키마 |
B 코드 줄바꿈이 \n 두 글자 |
파이프라인 코드 문법 검사 | Gemma 모델 (추정) |
| C 승인 때 계획 재작성 | plan_hash 비교 |
Gemma 모델 + 승인 방식 |
- A: Gemma는 형식대로
flag:true처럼 따옴표 없이 쓴다. vLLM v0.24부터 Gemma 파서가 바뀌며(#45588) 이 값을 전부 문자열로 둔다(vLLM 이슈 #47906, v0.30·main에서도 재현). 스키마에 타입이 적힌 경로만 되돌리는데, 우리payload가{"type":"object","additionalProperties":true}라 거기서 끊긴다. 수정 PR #47909는 리뷰 없이 11/6에 닫혔다. - B: Gemma 공식
chat_template.jinja는 글자 값을<|"|>로 감싸기만 한다.\n표기 규칙이 없다. Gemma가 JSON 버릇대로\n을 썼고, 파서는 규칙대로 그대로 꺼냈다. 실패한 턴 원문은 남지 않아 추정이다. - C:
plan_hash는 계획 전체로 계산한다. Gemma가 2,000자 넘는 계획을 승인 때 다시 쓰다 한 글자만 달라도 어긋난다. 확률 문제다(데모 환경에서 20번 중 5번).
🧭 해결 과정¶
- 10/27: 모델 능력 부족 의심 → 아니었다. 구조 오류는 고쳤고, 타입 오류에서만 같은 호출을 되풀이했다. 파서 소스와 보내는 스키마를 보고 갈래 A를 찾았다.
- 11/12: 실패 13건을 "마지막에 막힌 원인"으로 분류 → A를 고치면 앱·파이프라인·온톨로지 7건만 풀린다. 대시보드 5건은 타입 문제가 아니었다(C,
<|"|>표기 누수, 문자열 plan). - 로그 확인 → 파이프라인 요청 2번이 B로 끝났다. 같은 오류를 직접 재현했다. 검사는 첫 오류에서 멈추므로 뒤에 A가 숨어 있었는지는 모른다.
- 책임 나누기 → 공식 템플릿 기준으로 A는 파서, B는 모델로 정리했다.
- "우리 잘못은 없나" 확인 → 실제 보내는 스키마에서 타입이 정확히
payload에서 끊겼다. vLLM 버그가 원인이고, 타입 없는payload가 그 버그에 노출시킨 것이다.any로 둔 건 vLLM v0.23까지는 문제가 없었기 때문이다. 다만anyOf로 적으면 vLLM이 따라가지 않아 그대로 실패한다. - 재테스트(대시보드 2, 앱 2) → 대시보드 1번만 승인 카드. 계획을 똑같이 베껴 쓴 운이었다. 앱 2번은 A로 시작해 계획이 망가지며 422.
- 남은 할 일(미해결)
- 타입 되돌리기를 빠짐없이(#3561). 스키마로 하려면
anyOf없이 적어야 한다. - 승인은 계획 대신 제안 번호만 받기(C 제거).
- 코드 항목에서만, 문법 실패 +
\n치환 시 통과할 때만 줄바꿈 보정(B). - 남은
<|"|>표기와 문자열 plan 풀기.
✅ 다음에 같은 증상이 보이면¶
- 오류 문장으로 갈래부터 나눈다:
must be a number/boolean/integer→ A,line continuation character→ B,plan_hash→ C, 값에<|"|>가 남음 → 파서 표기 누수. - A면 같은 요청을 Claude로 보내 비교하고, 그 칸이 스키마에서
type까지 이어지는지 본다. 서빙 vLLM이 v0.24 이상인지도 확인한다. - 하나를 고친 뒤엔 꼭 다시 돌린다. 검사가 첫 오류에서 멈춰 다른 갈래가 숨어 있을 수 있다.
- 재발 방지: 모델 출력을 그대로 믿는 계약(계획 재전송, 타입 없는 객체)을 줄인다. 외부 엔드포인트·고객 내부망 vLLM은 우리가 못 바꾸므로 받는 쪽이 견디게 만든다.
🔗 관련¶
- 관련 개념: LLM 도구 호출 파서와 타입 복원, JSON Schema 규약과 해석, pydantic 검증과 Field 설정
- 참고 자료: Tool Calling (vLLM 공식 문서, 영문) · 링크 / vLLM 이슈 #47906 (영문) · 링크