함수 호출(Function Calling) - LLM에게 도구를 쥐여주기
LLM은 학습된 지식으로 그럴듯한 문장을 만들지만, 실시간 재고를 조회하거나 결제를 취소하거나 정확한 계산을 하지는 못합니다. 이 한계를 넘는 표준적인 방법이 함수 호출(Function Calling), 즉 LLM에게 도구(tool)를 쥐여주는 것입니다.
이 글에서는 함수 호출이 정확히 어떤 구조로 동작하는지, 스키마는 어떻게 정의하고 실행 루프는 어떻게 도는지를 정리합니다.
1. 가장 흔한 오해부터 풀기
먼저 확실히 해둘 것이 있습니다.
LLM은 코드를 직접 실행하지 않습니다.
LLM이 하는 일은 "어떤 함수를, 어떤 인자로 부르면 되겠다" 는 의도를 구조화된 JSON으로 내놓는 것뿐입니다. 실제 실행은 우리 애플리케이션 코드가 합니다.
[사용자] "서울 날씨 어때?"
│
[LLM] 실행❌ → "getWeather({ city: '서울' }) 를 부르면 될 것 같아" (JSON 출력)
│
[내 코드] 실제 API 호출 → { temp: 28, sky: "맑음" }
│
[LLM] 그 결과를 받아 "서울은 지금 28도, 맑아요" 라고 문장 생성
즉 함수 호출은 "LLM의 판단 + 개발자의 실행" 을 이어붙이는 프로토콜입니다. 이 분리가 안전성의 핵심입니다. LLM은 무엇을 할지 제안만 하고, 진짜 실행 권한은 우리가 쥡니다.
2. 스키마 정의 - 도구 설명서 건네기
LLM이 어떤 도구가 있는지 알려면, 각 함수의 이름·설명·인자 형태를 JSON Schema로 알려줘야 합니다. 이걸 도구 정의(tool definition)라고 합니다.
const tools = [
{
name: "get_weather",
description: "특정 도시의 현재 날씨를 조회한다",
input_schema: {
type: "object",
properties: {
city: { type: "string", description: "도시 이름, 예: 서울" },
unit: { type: "string", enum: ["c", "f"], description: "온도 단위" },
},
required: ["city"],
},
},
];
여기서 description이 정말 중요합니다. LLM은 이 설명만 보고 "언제 이 도구를 쓸지"를 판단합니다.
- 함수 이름과 설명은 사람이 읽어도 명확하게 씁니다.
- 각 인자에 설명·타입·
enum·를 촘촘히 채웁니다.