지난 글에서 xl-ai + 로컬 Gemma와의 진흙탕 싸움을 끝낸 뒤, 제 머릿속에는 하나의 결론이 새겨졌습니다. 로컬 모델은 tool calling을 도무지 못 한다. Gemma 3 27B는 안정적인 JSON조차 뱉지 못했고, "모델이 얌전히 tool-call 형식을 말해줄 것"이라 전제하는 라이브러리(xl-ai 같은)는 연결하는 순간 터졌습니다. 결국 백엔드에 손수 만든 어댑터를 끼워서 겨우 살려냈죠.

그러다 번뜩였습니다. 로컬 AI를 하는 사람이 전부 이 벽에 부딪힌다면, "tool-calling 복구 프록시"는 블루오션 아닐까?

구상은 단순했습니다. 앱과 로컬 모델 사이에 끼우는 OpenAI 호환 중간 레이어입니다. 앱은 평소처럼 표준 tools 형식으로 요청을 보낸다. 프록시가 tool 정의를 모델이 씹을 수 있는 프롬프트로 번역하고, 모델이 뱉은 깨진 JSON을 주워서 복구하고, 스키마로 검증하고, 실패하면 자동 재시도 ― "tool calling을 못 하는 모델"을, 밖에서 보면 "할 수 있는 모델"처럼 보이게 만든다.

아름다운 이야기로 들렸습니다. Vercel AI SDK나 LangChain으로 로컬 모델을 연결하는 사람은 다들 바로 이걸로 불평하는데, 독립적인 복구 레이어는 시장에 존재하지 않았으니까요.

하지만 이번엔 학습했습니다. 손을 대기 전에, 먼저 하루를 들여 벤치마크를 작성하고 가설을 검증한다.

그 벤치마크가, 제 손으로 만든 제 아이디어를 처형했습니다. 이 글은 그 완전한 부검 보고서입니다 ― 덤으로, 2026년 6월 시점에 로컬 tool-calling 모델을 고르기 위한 실용 스코어보드도 붙여 둡니다.

(먼저 검증하려 마음먹은 것 자체가 지난 싸움의 교훈이었습니다. 공식 라이브러리의 아름다운 데모 뒤에는 늘 강력한 백엔드라는 전제가 숨어 있죠. 이번엔 내가 "라이브러리 작가" 쪽이 되는 셈이니, 내가 전제로 삼은 "약한 백엔드"가 정말 존재하는지 먼저 확인해야 한다고.)


벤치마크 설계#

테스트 케이스는 25개, 6개 카테고리. 전부 실제 제품에서 마주치는 형태입니다:

카테고리무엇을 시험하는가
simple5단일 도구 직구: 날씨, 메일 전송, SQL (비영어 프롬프트 포함)
complex5중첩 스키마: 참석자가 객체 배열이고 required가 붙은 캘린더 이벤트
html4HTML-in-JSON: xl-ai의 applyDocumentOperations 그 자체 ― JSON 필드에 HTML 문자열, 따옴표 이스케이프 지옥
parallel3하나의 요청에 2회 이상의 call이 필요: "도쿄와 런던 날씨를 비교해줘"
nocall4부르지 않는 자제력: "날씨와 기후의 차이는?" (get_weather 도구를 쥐여준 상태에서)
select4도구 4개를 늘어놓고 올바른 것을 고를 수 있는가

각 모델은 2가지 조건으로 돌립니다:

  • native: Ollama의 OpenAI 호환 API + tools 파라미터 ― 즉 Vercel AI SDK / LangChain이 오늘 실제로 보내는 것.
  • shim: "복구 프록시"의 시뮬레이션 ― tools는 넘기지 않고, system prompt로 JSON만 출력하도록 요구. 응답에 대해 펜스 제거, <think> 블록 제거, 괄호 균형 스캔으로 JSON 추출, jsonrepair로 복구, ajv로 스키마 검증, 오류 내용을 덧붙인 1회 재시도를 수행한다.
js
// shim의 핵심: JSON 추출(think 블록 제거, 펜스 제거,
// 괄호 균형 스캔) → 복구 → 검증 → 재시도
function parseShimOutput(text) {
  const raw = extractJson(text)        // 첫 번째 괄호 균형이 맞는 {...}를 찾는다
  let obj
  try {
    obj = JSON.parse(raw)
  } catch {
    obj = JSON.parse(jsonrepair(raw))  // 깨진 JSON의 응급실
  }
  // ...이후 ajv 스키마 검증, 실패하면 오류 메시지를 붙여 1회 재시도
}

채점은 "call하면 합격"이 아닙니다. 도구 이름이 맞아야 하고, 인자가 선언된 스키마의 ajv 검증을 통과해야 하고, 내용 체크를 통과해야 하고(메일이 정말 bob@example.com으로 갔는가), parallel은 필요한 수의 call을 전부 내야 하고, nocall 케이스에서 단 한 번이라도 call하면 즉사. temperature: 0, 전부 RTX 5060 Ti 16GB 한 장 + Ollama, 양자화는 각 모델의 기본 Q4입니다.

(솔직히 말해두면: 25케이스, 1패스, Q4 양자화 ― 이건 학술 논문이 아니라 스모크 테스트입니다. 다만 아래 숫자를 보면 동의하실 겁니다. 0/25와 25/25의 차이는 노이즈로 설명할 수 있는 게 아닙니다.)


스코어보드#

native 점수 순:

모델nativeshim비고
qwen3.5:4b25/2522/25만점, 게다가 4B
qwen3.5:9b25/2525/25만점
granite4.1:8b ✨24/2524/25IBM의 새 세대
ministral-3:8b ✨24/2524/25Mistral의 엣지 라인
granite4:tiny-h22/2512/25shim으로 반토막
lfm2.5:8b ✨22/255/25"tool calling을 위해 태어났다"고 자칭; shim에서 5까지 붕괴
mistral-nemo22/2525/252024년의 고참, 의외로 싸운다
nemotron-3-nano:4b ✨21/2523/25NVIDIA의 에이전트 라인
qwen3.6:27b ✨21/2523/25집안의 4B 막내에게 진다, 후술
gemma4:e4b20/2524/25
gemma4:e2b19/2518/25
gemma4:12b18/2520/25html 카테고리 0/4, 일족의 저주
hermes3:8b18/2523/25
qwen3:4b16/2517/25qwen3.5:4b와 비교: 한 세대 차이 = 9문제
phi4-mini12/2522/25
llama3.2:3b9/2516/25
functiongemma:270m ✨7/250/25270M 특화 모델, 네이티브 형식만 인정
command-r7b4/2511/25native의 4점은 전부 nocall의 "침묵 득점"
deepseek-r1:8b4/2523/25thinking 모델의 이중인격, 후술
glm4:9b4/2513/25command-r7b처럼 침묵으로 번다
gemma3:12b0/2523/25API가 400을 직접 반환: not support tools
gemma3:4b0/2519/25위와 동일

(✨ = 2026년 5~6월 출시된 새 모델, 집필 주간에 pull해서 실측)


발견 1: 지난 싸움은 억울한 게 아니었다 ― Gemma 3는 정말로 0점#

먼저 맨 아래 두 줄부터. Gemma 3의 native는 0/25 ― 게다가 답을 틀린 게 아니라, Ollama의 API가 400을 직접 반환합니다:

registry.ollama.ai/library/gemma3:12b does not support tools

모델의 chat template에 tool이라는 개념이 애초에 존재하지 않는다. 즉 지난번 xl-ai와 싸운 그 전쟁은, 첫날부터 이길 수 없었던 것입니다. 내 연결이 나빴던 게 아니라, 그건 API 레벨에서 존재하지 않는 것이었다. 이 0/25를 본 순간, 뒤늦은 명예 회복 같은 기분이 들었습니다.

그리고 shim은 gemma3:12b를 0점에서 23/25까지 끌어올립니다. 다시 말해 ― 내 복구 프록시 아이디어는 2025년 세대 모델에 대해서는 완전히 성립했다. 프롬프트 전략 + JSON 복구 + 재시도로, "API가 tools를 거부하는 모델"에게 92%를 내게 할 수 있다.

이 벤치마크를 2025년 중반에 돌렸다면, 아마 저는 개발을 시작했을 겁니다.


발견 2: 세대의 단층 ― 창문은 Qwen3.5가 나온 날 닫혀 있었다#

다음으로 맨 위 두 줄.

qwen3.5:4b, native 25/25, 만점. 4B 모델입니다. 3.4GB. 제 중산층 GPU에서 가볍게 돌아갑니다.

"대충 쓸만하다"가 아니라 25문제 전문 정답: 중첩 스키마도 전부, HTML-in-JSON도 전부(<b>final</b> 태그를 유지하라는 문제까지), 병렬해야 할 때 병렬하고, 침묵해야 할 때 침묵하고, 4지선다 도구 선택도 전문 정답. 결과 JSON을 노려보며 한 문제라도 틀린 걸 찾으려 했지만, 없었습니다.

같은 패밀리 내 비교는 더 잔혹합니다:

  • qwen3 → qwen3.5(같은 4B): 16/25 → 25/25
  • gemma3 → gemma4(같은 12B): 0/25 → 18/25("API가 거부"에서 "대충 동작"으로)

단 1년 만에, 네이티브 tool calling은 "로컬 모델의 집단적 장애"에서 "새 세대의 기본 장비"가 되었다. 지난 글에 쓴 "우리 로컬 Gemma는 tool calling이 도무지 안정적이지 않다"는 문장은, 2025년 모델에는 사실이었지만 2026년 모델에는 이미 낡은 정보입니다.


발견 3: 복구 레이어는 새 모델에게 오히려 감점 요인#

이게 프록시 아이디어에 꽂힌 두 번째 칼이고, 더 깊이 박힙니다.

qwen3.5:4b를 보세요: native 25/25, shim 22/25. 같은 모델에 제 "복구 레이어"를 씌웠을 뿐인데 3문제가 떨어진다. granite4:tiny-h는 더 비참합니다: 22/25가 12/25까지 내동댕이쳐진다.

이유는 어렵지 않습니다. 새 세대 모델은 훈련 단계에서 공식 tool-calling 형식(자체 chat template, 자체 특수 토큰)에 깊이 정렬되어 있다. 제 shim은 tool 정의를 "JSON만으로 답하라"는 토착 프롬프트로 번역한다 ― 정규 훈련을 받은 사람에게, 내가 발명한 방언으로 말하라고 강요하는 셈입니다. 네이티브 능력이, 제 "친절"에 의해 방해받는다.

이건 프록시의 가치 제안에 치명적입니다:

  • 구형 모델(gemma3)에 대해: shim의 압승, 0 → 23. 하지만 그렇다면 ollama pull qwen3.5 한 방이면 되지 않나?
  • 신형 모델(qwen3.5)에 대해: shim은 마이너스 자산.

복구 프록시의 유일한 서식지는 "구형 모델에 묶여 있고, tool calling이 필요하고, 모델을 바꿀 수 없는" 사용자뿐. 이 교차점은 프로젝트를 지탱하기엔 너무 좁다.


발견 4: 케이스 스터디 ― 모델마다 저마다의 죽는 법(과 사는 법)이 있다#

deepseek-r1:8b의 이중인격: native 4/25(네이티브 tool call을 거의 내지 않고, 추론이 끝나면 자연어로 그냥 답한다), shim 23/25(JSON을 요구하면 순순히 뱉는다, 게다가 고품질). thinking 모델은 shim 조건의 최대 수혜자입니다 ― <think> 블록을 벗기면, 안의 JSON은 오히려 깨끗합니다.

command-r7b와 glm4:9b의 "침묵 득점": native 각 4/25, 게다가 그 4점은 전부 nocall 카테고리에서 나온다 ― 즉 벤치마크 전체를 통틀어 tool call을 단 한 번도 내지 않고, "불러서는 안 되는" 문제에서 침묵한 것만으로 4점을 주웠다. tools 태그는 그냥 장식입니다.

HTML-in-JSON은 Gemma 일족의 저주: gemma4는 3개 사이즈 모두 html 카테고리에서 0/4. 전형적인 실패는 스키마 검증이 operations[0]type 필드 누락을 잡아내는 패턴 ― HTML 이스케이프의 압력 아래 구조가 붕괴한다. 바로 xl-ai의 시나리오 그 자체: gemma4를 BlockNote에 연결해도, 피와 눈물은 리플레이됩니다. 같은 문제에서 qwen3.5는 4/4. 모델 선택을 잘못하면, 프레임워크 쪽에서 어떻게 연결해도 헛수고입니다.

mistral-nemo, 노병은 죽지 않는다: 2024년 중반의 12B 고참이 native 22/25. 당시 Mistral은 function calling을 훈련의 일급 목표로 삼았고, 2년 후인 지금도 새 세대와 팔씨름을 한다. tool calling이 훈련 목표에 들어 있었는지 여부는, 파라미터 수보다 무겁다.


2026년 6월의 신병들: 세대 통째로 아이디어의 반대편에 줄섰다#

집필한 주간에, Ollama library의 2026년 5~6월 출시 새 모델(16GB 카드에 올라가는 것)을 전부 pull해서 추가 실측했습니다. qwen3.5의 만점이 "점"이었다면, 신병들이 이은 건 "선"입니다:

granite4.1:8b와 ministral-3:8b가 나란히 24/25. IBM과 Mistral의 새 세대가, 둘 다 만점까지 한 문제. tool calling은 이제 Qwen의 전매특허가 아니라, 새 세대의 입장료입니다.

lfm2.5:8b는 표에서 가장 드라마가 있는 줄: Liquid AI의 공식 카피는 바로 "소비자용 하드웨어에서 빠르고 신뢰성 있는 tool calling을 위해"라고 내세우고, native 22/25는 그 간판에 부끄럽지 않다 ― 그런데 shim에서는 5/25까지 붕괴, 전체 표 최대의 역낙차입니다. 네이티브 형식에 지나치게 특화된 모델에게 토착 JSON 프롬프트를 억지로 먹이면, 말 자체를 잊는다. qwen3.5의 3문제 하락보다 폭력적으로, 같은 하나의 사실을 증명합니다: 복구 레이어는 새 모델에게 중립이 아니다. 독이다.

qwen3.6:27b, 이번에 가장 비싸게 먹힌 한 문제, 21/25 ― 집안의 4B 막내에게 패배. 17GB 모델을 16GB 카드에 욱여넣으면, 레이어의 14%가 CPU로 쫓겨나고, 추론 속도는 한 자릿수 떨어지고, 50회 추론을 끝내는 데만 2시간 가까이 걸렸습니다. 그래서 점수는 qwen3.5:4b(3.4GB, 전부 VRAM 내, 폭속)보다 4문제 낮다. 소비자용 GPU에서 로컬 AI를 하는 모두에게 한 줄: VRAM에 들어가는 "작고 새로운"이, 넘쳐나는 "큰"을 이긴다. 파라미터 수의 허영은, 오프로딩 앞에서 무가치합니다.

functiongemma:270m은 덤 같은 소재: Google제 270M function-calling 특화 모델, native 7/25(이 사이즈치고는 훌륭), shim 0/25 ― 모델 전체가 네이티브 형식을 중심으로 증류되어 있어서, 그 형식 밖에서는 한 문제도 살아남지 못한다. 특화 모델의 극단적 견본으로서, "형식 고착(lock-in)" 현상의 대조군을 마침 맡아 주었습니다.


판결: 아이디어는 죽었다. 하지만 좋은 죽음이었다#

그 복구 프록시로 돌아갑니다. 사망 진단서는 3줄:

  1. 문제 자체가 자연 소멸하고 있다. tool-calling 복구는 "모델 세대의 문제"이지 "구조적 문제"가 아니다. 새 세대 오픈 모델(선두는 qwen3.5)은 이미 네이티브로 해결했고, 앞으로도 좋아지기만 한다.
  2. 복구 레이어는 새 모델에 마이너스 자산. 타깃 사용자가 모델을 업데이트하는 순간, 당신의 제품은 "도움"에서 "방해"로 바뀐다.
  3. 대체 수단은 명령어 한 줄. ollama pull qwen3.5:4b, 3.4GB, 무료. 어떤 프록시의 도입 비용도 이걸 이길 수 없다.

그래서 이 프로젝트는 제품 코드 첫 줄을 쓰기 전에 처형되었습니다. 집행인은 직접 쓴 25개의 테스트 케이스. 소요 시간, 하루.

오늘부터 로컬에서 tool calling을 한다면, 실용 조언은 4줄로 압축됩니다:

  • 고민되면 이것: qwen3.5(4b로 이미 만점, VRAM 예산이 있으면 9b); granite4.1:8b와 ministral-3:8b는 합격점의 차선책
  • VRAM에 안 들어가는 대형 모델을 억지로 올리지 마라: qwen3.6:27b는 16GB 카드 위에서 느린 데다 4B에게 진다
  • thinking 모델을 쓴다면: deepseek-r1은 자체 JSON 출력 레이어가 필수, native tool call에는 손대지 마라
  • 지뢰: 구조화 출력에서의 gemma 전 시리즈(특히 페이로드에 HTML/markup을 포함할 때), 그리고 2025년 상반기 이전 모델 전부

마지막으로#

지난 글의 맺음에 이렇게 썼습니다. "공식 라이브러리의 아름다운 데모 뒤에는, 대개 아주 강력한 백엔드라는 전제가 숨어 있다." 이 글은 그 거울상입니다: 스타트업 아이디어의 아름다운 청사진 뒤에는, 대개 "변하지 않는 세계"라는 전제가 숨어 있다. 저는 "로컬 모델은 tool calling을 못 한다"는 고통이 지속될 거라 가정했지만, 모델 세대의 교체는 제가 프록시를 다 쓰는 속도보다 빨랐습니다.

하루의 벤치마크가, 1~2개월을 잡아먹었을 프로젝트를 죽였다. 실패처럼 들리지만, 사실은 올해 가장 수지맞는 거래였습니다 ― 검증의 가치는 내가 옳다는 걸 증명하는 게 아니라, 아직 쌀 때 내가 틀렸다는 걸 발견하는 데 있다.

벤치마크 코드와 전체 22개 모델의 원시 결과 JSON은 GitHub에 있습니다. 25개 케이스는 실제 제품에서 마주치는 형태(xl-ai식 HTML-in-JSON 킬러 문제 포함)를 커버합니다. 손에 든 모델로 돌려 보세요 ― 만약 그중 하나가 제 표를 후려친다면, 알려 주세요. 맞는 건 환영입니다. 이젠 익숙해졌거든요.