로컬 LLM에 도구를 쥐여주기 — 함수 호출(tool calling) 첫걸음
요약: 로컬 LLM이 계산, 검색, DB 조회 같은 실제 작업을 수행할 수 있게 하려면 모델이 도구를 직접 실행하는 대신 '이 도구를 이 인자로 호출해 달라'는 구조화된 JSON 요청만 생성하게 하고, 그 실행과 결과 반환은 애플리케이션 코드가 전담하는 전체 흐름을 만들어야 한다. 한 줄 요약: 로컬 LLM은 도구를 직접 실행하지 않고 '호출 요청'만 출력하며 실제 작업 실행은 우리 앱이 책임진다.
로컬 LLM이 계산, 검색, DB 조회 같은 실제 작업을 수행할 수 있게 하려면 모델이 도구를 직접 실행하는 대신 '이 도구를 이 인자로 호출해 달라'는 구조화된 JSON 요청만 생성하게 하고, 그 실행과 결과 반환은 애플리케이션 코드가 전담하는 전체 흐름을 만들어야 한다.
한 줄 요약: 로컬 LLM은 도구를 직접 실행하지 않고 '호출 요청'만 출력하며 실제 작업 실행은 우리 앱이 책임진다.
로컬 LLM은 도구를 직접 실행하지 않는다 — 모델은 '이 도구를 이 인자로 호출하라'는 구조화된 JSON 요청만 내고, 실제 실행·결과 반환은 앱 코드가 한다
왜 모델이 도구를 '직접' 실행하지 않을까?#
대형 언어 모델은 다음에 올 단어를 통계적으로 예측하는 시스템일 뿐이다. 모델 내부에는 외부 프로그램을 호출하거나 파일을 읽거나 계산을 수행할 수 있는 실행 환경이 전혀 없다. 따라서 모델은 '무엇을 해야 하는지'를 계획하거나 요청하는 텍스트·구조를 출력할 수 있을 뿐, 실제 행동은 언제나 외부 코드가 수행해야 한다.
초보자 비유로 쉽게 이해해보자. 모델은 요리사에게 "소금 2g 넣어줘"라고 적힌 주문서를 작성해 전달하는 역할만 한다. 실제로 소금통을 열고 정확히 계량해서 넣는 행동은 주방(우리 앱 코드)이 담당한다. 모델이 직접 주방에 들어가 소금을 집을 수는 없는 것이다.
세 가지 접근은 어떻게 다를까?#
로컬 LLM에 도구를 붙이는 방법은 모델이 도구 사용 의사를 어떻게 표현하느냐, 그 표현을 실제 실행으로 연결하는 책임이 어디에 있느냐에 따라 크게 세 가지로 나뉜다. 아래 비교표는 2026년 7월 기준 주요 런타임의 정성적 특성을 정리한 것이다.
| 접근 | 무엇을 하나 | 대표 지원 | 초보 난이도 | 주의점 |
|---|---|---|---|---|
| 프롬프트로 흉내 | 모델이 "이 툴 써"라는 텍스트만 냄, 실행·파싱은 앱이 직접 | 사실상 전부 | 낮음 | 출력 파싱이 깨지기 쉬움 |
| 네이티브 tool calling | 모델이 구조화된 tool_call(JSON)을 반환 | Ollama·llama.cpp·OpenAI 호환 | 중간 | 도구 스키마를 미리 정의해야 |
| 에이전트 루프(ReAct) | 생각→도구 호출→결과 관찰을 반복 | LangChain 등 프레임워크 | 높음 | 무한 루프·토큰 비용 관리 필요 |
프롬프트로 흉내 내는 방식은 가장 오래된 접근이다. 모델에게 미리 "계산이 필요하면 CALCULATE: 식 형태로 출력해"라고 지시하면 모델이 자유로운 텍스트로 도구 사용 의사를 표현한다. 앱은 그 텍스트를 정규식이나 문자열 파싱으로 해석해 실제 함수를 호출한다. 코드가 간단하고 어떤 모델에서도 바로 쓸 수 있지만, 모델 출력이 조금만 달라져도 파싱이 실패하는 경우가 잦다.
네이티브 tool calling은 Ollama, llama.cpp 등 최근 로컬 런타임이 공식 지원하는 기능이다. 미리 도구 이름, 설명, 파라미터 스키마(JSON Schema)를 정의해 모델에게 알려주면, 모델이 필요할 때 정확한 형식의 tool_call JSON을 출력한다. 앱은 이 JSON을 신뢰성 있게 파싱해 함수를 실행하고 결과를 다시 모델 컨텍스트에 넣어준다. 출력이 구조화되어 파싱 오류가 크게 줄지만, 도구 스키마를 정확하고 상세하게 작성해야 하는 준비 작업이 필요하다.
에이전트 루프(ReAct)는 모델이 '생각 → 도구 호출 → 결과 관찰'의 사이클을 여러 번 반복하며 복잡한 문제를 단계적으로 풀도록 만든다. LangChain 같은 프레임워크가 이 루프를 자동으로 관리해 주기 때문에 개발자가 직접 루프 코드를 짤 필요가 적다. 한 번의 호출로 끝나지 않는 다단계 작업에 매우 강력하지만, 루프가 무한히 돌거나 토큰 비용이 급증할 위험이 있어 중단 조건과 비용 제어 로직이 필수적이다.
무엇부터 시작해야 하나?#
로컬 환경에서 처음 tool calling을 도입할 때는 난이도와 목적에 따라 순서를 정하는 것이 좋다.
- 가장 먼저 네이티브 tool calling을 1개 도구부터 시도하라. Ollama나 llama.cpp 최신 버전에서 지원하는 tool calling 기능을 확인하고, 단순한 계산기나 날짜 포맷 변환 도구 하나만 정의해 테스트하면 구조화된 호출의 안정성과 편리함을 바로 경험할 수 있다.
- 하나의 도구 호출로 해결되지 않는 복잡한 다단계 작업(검색 → 분석 → 저장 등)이 필요하다면 에이전트 루프를 검토하라. LangChain이나 유사 프레임워크를 사용하면 ReAct 스타일 에이전트를 비교적 빠르게 구성할 수 있다.
- 아이디어를 빠르게 검증하는 프로토타입 단계라면 프롬프트로 흉내 내는 방식으로 시작해도 충분하다. 나중에 실제 서비스화할 때 네이티브 tool calling으로 점진적으로 업그레이드하면 된다.
이렇게 단계적으로 접근하면 모델이 '계획을 세우는 역할'과 앱이 '그 계획을 실행하는 역할'을 명확히 분리하는 원리를 자연스럽게 익힐 수 있다.
참고: 2026-07-12 KST 기준으로 Ollama, llama.cpp, vLLM 등 주요 로컬 LLM 런타임의 tool calling 지원 여부와 출력 형식(JSON 스키마, 특수 토큰 사용 여부 등)은 버전마다 차이가 있을 수 있다. 실제 배포 전 사용 중인 런타임의 최신 공식 문서를 반드시 확인하고 호환성 테스트를 진행하기 바란다.
이 시리즈: 이 시리즈 허브 — 로컬 LLM·RAG 7가지 관문 · 구조화 출력으로 파싱 실패 없애기 · speculative decoding로 속도 올리기
함께 읽기: 로컬 LLM에 JSON을 강제하기 — 구조화 출력으로 파싱 실패 없애기, Phi-4-mini 3.8B로 128K 장문을 로컬에서 처리한다
참고 링크
Responses
No responses yet. Be the first to respond.