Gemma 4 tool calling이 흔들린 이유, LocalLLaMA가 잡아낸 작은 Jinja 버그

LocalLLaMA가 좋아하는 글은 막연한 불평을 재현 가능한 버그로 바꾸는 글이다. 이번 Gemma 4 tool calling 스레드가 딱 그랬다. 출발점은 익숙하다. 여러 inference engine에서 커스텀 MCP 도구를 붙였을 때 Gemma 4만 유난히 불안정했고, Qwen3.5와 gpt-oss-20b는 멀쩡했다. 보통 여기서 "Gemma가 좀 별로다"로 끝나기 쉽다. 그런데 작성자는 verbose log를 파고, prompt rendering을 비교하고, 결국 chat template 안의 의외로 작은 실패 지점을 끄집어냈다.

핵심은 Jinja template가 흔한 JSON Schema 형태를 다루는 방식이었다. tool parameter가 anyOf: [$ref, null] 같은 패턴을 쓰면 중요한 구조는 anyOf와 $defs 안에 들어 있다. 그런데 template는 top-level type이 있다고 가정했다. 그 결과 렌더링된 prompt에서는 parameter 정보가 빈 type 필드처럼 납작해졌고, 모델은 tool call에 필요한 맥락을 잃었다. 작성자 설명대로라면 작은 수정만으로 이 정보가 다시 살아났고, 이후에는 oneOf, allOf, $defs, enum, const, type array, null 값까지 보존 범위를 넓혔다.

댓글 수는 많지 않지만 왜 의미가 있었는지는 바로 드러난다. 사람들이 곧장 물은 건 "그래서 Gemma 4가 agent setup에서 Qwen3.6보다 흔들린 이유가 이거였나", "전 모델에 걸친 문제인가", "로컬 패치만으로 정상화되나"였다. 이런 대화는 benchmark 스크린샷보다 훨씬 값지다. 막연한 모델 선호를 테스트 가능한 소프트웨어 버그로 바꾸기 때문이다. 동시에 LocalLLaMA가 반복해서 겪는 패턴도 보여준다. 겉으로는 모델 품질 문제처럼 보이지만, 실제 원인은 weights 아래쪽의 template, runtime, formatting, tooling 층에 숨어 있는 경우가 많다.

그래서 이 글은 점수가 아주 높지 않아도 멀리 갔다. 바로 손댈 수 있는 힌트를 줬기 때문이다. tool schema가 prompt에서 어떻게 렌더링되는지 직접 본다. nested JSON Schema 의미가 template에서 자동으로 보존된다고 믿지 않는다. 실패한 모델과 잘 되는 모델을 출력 결과만이 아니라 prompt 단계에서 비교한다. LocalLLaMA가 이런 글에 반응하는 이유는 분명하다. 체감 불만과 실제 수정 사이 거리를 확 줄여주기 때문이다. benchmark 소음이 많은 곳일수록 작은 버그 리포트가 더 또렷하게 보인다.