
Spring AI Tool Calling 설명 | LLM에 진정한 초능력을 부여하는 방법
요약
LLM의 학습 데이터 한계를 극복하기 위한 Tool calling의 개념과 작동 원리를 설명합니다. Spring AI를 활용하여 모델이 외부 데이터나 API를 호출할 수 있도록 설계하는 에이전트 구축의 핵심 과정을 다룹니다.
핵심 포인트
- Tool calling은 LLM이 외부 도구를 사용하여 실시간 데이터에 접근하는 방식입니다.
- LLM은 코드를 직접 실행하는 것이 아니라 실행이 필요한 함수를 요청합니다.
- 실제 함수 실행과 결과 반환은 애플리케이션 단계에서 처리됩니다.
- Spring AI를 통해 도구 목록을 정의하고 모델과 상호작용하는 루프를 구현합니다.
ChatGPT에게 지금 Apple의 주가가 얼마인지 물어보세요. 그러면 실시간 데이터가 없다고 답하거나, 몇 달 전의 숫자를 자신 있게 말할 것입니다. 이것은 모델의 버그가 아닙니다. LLM (Large Language Models)이 작동하는 방식의 근본적인 한계입니다. LLM은 특정 날짜까지의 데이터로 학습되어 봉인된 채 출시됩니다. 이들은 당신의 데이터베이스를 호출할 수 없습니다. 날씨를 확인할 수 없습니다. 고객의 주문 상태를 조회할 수도 없습니다. 이들은 학습 중단 시점(training cutoff) 이후의 세상에 대해서는 아무것도 모르며, 당신의 세상에 대해서는 전혀 알지 못합니다.
Tool calling (도구 호출)은 바로 그 문제를 해결하는 방법입니다. 그리고 이는 사람들이 AI 에이전트 (AI agents)라고 부르는 것의 핵심 구성 요소 중 하나입니다. 즉, LLM이 단순히 텍스트를 생성하는 것을 넘어 행동을 취할 수 있는 시스템을 의미합니다.
Tool calling의 실제 개념
Spring AI를 깊이 파고들기 전에, 쉬운 언어로 개념을 명확히 정리해 봅시다.
매우 똑똑한 컨설턴트를 고용했다고 상상해 보세요. 그들은 전략, 프레임워크, 역사, 글쓰기 등 많은 것을 알고 있습니다. 하지만 그들은 당신 회사의 내부 시스템에 접근할 권한이 없습니다. 그들은 당신의 CRM에 로그인하여 고객이 지난달에 결제를 했는지 확인할 수 없습니다.
그래서 당신은 다음과 같은 약속을 합니다. 질문에 답하기 위해 내부 데이터가 필요할 때마다, 그들은 당신에게 "주문 번호 #4521의 상태가 필요합니다"라고 적힌 포스트잇을 건넵니다. 당신은 그것을 찾아보고, 답을 가져다줍니다. 그러면 그들은 그 답을 사용하여 응답을 완성합니다.
이 포스트잇을 주고받는 과정이 바로 Tool calling입니다. 읽고 있는 LLM 제공업체의 문서에 따라 Function calling (함수 호출)이라고 불리기도 하지만, 개념은 동일합니다.
LLM은 코드를 직접 실행하지 않습니다. API를 직접 호출하지도 않습니다. 당신의 데이터베이스를 직접 건드리지도 않습니다. 그저 "이 특정 함수로부터 이 특정 정보가 필요합니다"라고 말할 뿐입니다. 실제 실행은 당신의 애플리케이션이 처리하고 결과를 반환합니다. 그러면 LLM은 그 결과를 사용하여 최종 응답을 작성합니다.
모델이 요청하고, 당신의 앱이 실행한다는 이 차이점 — 이것이 Tool calling에 대해 이해해야 할 가장 중요한 사항입니다. 그리고 대부분의 튜토리얼은 이 부분을 그냥 지나쳐 버립니다.
5단계 루프 — 모든 Tool call 내부에서 일어나는 일
Spring AI Tool call
위 다이어그램은 전체 사이클을 보여줍니다. 각 단계를 쉬운 용어로 설명하면 다음과 같습니다:
1단계 — 사용자가 질문을 합니다. "지금 애플의 주가는 얼마인가요?"
2단계 — LLM (Large Language Model)이 질문과 사용자의 도구 목록 (tool list)을 읽습니다. Spring AI에 도구를 등록할 때, 각 도구는 이름과 설명을 가집니다. LLM은 이 설명들을 읽고 결정을 내립니다: '내가 가진 학습 데이터로 답변할 수 있는가, 아니면 도구를 호출해야 하는가?'
3단계 — LLM은 답변이 아닌 도구 요청 (tool request)을 반환합니다. 이 부분이 사람들이 예상하지 못하는 지점입니다. 질문에 답하는 대신, 모델은 다음과 같은 내용을 보냅니다: "인자(argument) AAPL을 사용하여 getStockPrice 함수를 호출하세요." 단지 도구 이름과 인자일 뿐입니다. 아직 답변은 없습니다.
4단계 — 사용자의 Spring 애플리케이션이 해당 메서드를 실행합니다. Spring AI가 도구 요청을 포착하여, 코드 내에서 일치하는 @Tool 메서드를 찾아 호출하고, 연결된 API나 데이터베이스 등으로부터 실제 데이터를 가져옵니다.
5단계 — 결과가 다시 LLM으로 전달됩니다. Spring AI는 함수의 반환 값 (return value)을 대화 문맥 (conversation context)에 다시 보냅니다. LLM은 이를 읽고 이제 실제 응답을 작성합니다: "애플은 오늘 아침 기준으로 213.45달러에 거래되고 있습니다."
사용자는 근거가 확실한 실제 답변을 보게 됩니다. 환각 (hallucination)도 없고, 오래된 학습 데이터도 없습니다. 모델은 지능을 제공했고, 애플리케이션은 데이터를 제공했습니다.
@Tool 어노테이션: Spring AI에서 도구 등록하기
Spring AI에서 일반적인 Java 메서드를 LLM이 호출할 수 있는 도구로 바꾸는 데는 단 하나의 어노테이션 (annotation)이 필요합니다:
@Component
public class StockPriceTool {
...
여기서 대부분의 튜토리얼이 설명하지 않는 두 가지 사항이 있습니다:
@Tool에 작성하는 설명(description)은 사용자를 위한 문서가 아니라, LLM을 위한 지침(instructions)입니다. 모델은 이 텍스트를 읽고 언제 귀하의 함수를 호출할지 결정합니다. "주식 데이터를 가져옵니다"와 같이 모호한 설명은 모델을 혼란스럽게 할 것입니다. 반면 "주어진 티커 심볼(ticker symbol)에 대한 현재 주가를 가져옵니다"와 같이 정밀한 설명은 모델에게 이 도구가 정확히 언제 적용되는지를 알려줍니다. 이를 주석(comment)이 아닌 프롬프트(prompt)처럼 취급하세요.
@ToolParam 역시 마찬가지입니다. LLM은 어떤 값을 전달해야 하는지 이해하기 위해 파라미터 설명(parameter descriptions)을 읽습니다. 설명이 불분명하면 모델은 잘못된 인자(arguments)를 전달할 것입니다. 잘못된 입력(Bad input)은 잘못된 출력(bad output)으로 이어집니다.
ChatClient에 도구 등록하기
도구 클래스가 준비되면, 설정을 통해 ChatClient에 연결합니다:
@Configuration
public class AiConfig {
...
Spring AI는 빈(bean)을 스캔하여 @Tool 어노테이션이 붙은 모든 메서드를 찾아 자동으로 등록합니다. 장황한 스키마 정의(schema definitions)나 수동 타입 매핑(manual type mappings)도 필요 없습니다. 어노테이션이 붙은 메서드와 설정 파일의 코드 한 줄이면 충분합니다.
LLM이 읽는 것과 설명의 품질이 결정적인 이유
모델의 관점에서 어떤 일이 일어나는지 생각해 보세요. 질문에 답하기 전에 모델은 다음과 같은 내용을 보게 됩니다:
사용 가능한 도구:
- getStockPrice: 주어진 티커 심볼(ticker symbol)에 대한 현재 주가를 가져옵니다
- getWeatherForecast: 특정 도시의 3일간 일기 예보를 가져옵니다
...
이 설명 목록은 모델이 귀하의 도구가 무엇을 할 수 있는지에 대해 알 수 있는 유일한 정보입니다. 모델은 이 설명들을 사용하여 현재 질문에 어떤 도구(혹은 도구가 있는지 없는지)가 적용되는지 추론합니다.
만약 누군가 "내일 뭄바이(Mumbai)에 비가 올까요?"라고 묻는다면, 모델은 설명을 읽고 이를 getWeatherForecast와 매칭하여 호출합니다.
만약 누군가 "고객 미팅에 우산을 가져가야 할까요?"라고 묻는다면, 모델은 "고객 미팅"이 날씨에 대한 우려를 내포하고 있음을 추론하고, 사용자의 위치가 뭄바이임을 합리적인 가정으로 결정한 뒤, 그에 따라 적절한 도구를 호출해야 합니다.
이 모든 추론 과정은 여러분이 작성한 설명 문자열(description strings)을 바탕으로 이루어집니다. 설명을 잘 작성하면 모델은 진정으로 유용해집니다. 반대로 부주의하게 작성하면 모델은 잘못된 도구를 호출하거나, 잘못된 인자(arguments)를 전달하거나, 사용해야 할 도구를 건너뛰게 됩니다.
LLM이 도구를 체이닝(chaining)할 때와 보안
여러 도구를 등록하고 나면 흥미로운 일이 발생합니다. LLM이 도구들을 순차적으로 호출할 수 있게 되는 것입니다. 예를 들어, 먼저 getCustomerProfile을 호출하여 고객의 계정 등급을 가져온 뒤, 그 등급을 사용하여 결과를 필터링하도록 getOrderHistory를 호출하고, 마지막으로 해당 이력을 바탕으로 calculateDiscount를 호출할 수 있습니다.
이것이 바로 사람들이 말하는 AI 에이전트(AI agents)의 기초입니다. 즉, 단순히 질문에 답하는 것을 넘어 도구들을 조합하여 다단계 작업(multi-step tasks)을 완료하는 LLM을 의미합니다. Spring AI의 이러한 접근 방식은 도구를 일관되고 발견 가능한 방식으로 LLM에 노출하기 위한 표준인 MCP (Model Context Protocol)와 자연스럽게 연결됩니다. 하지만 이는 다른 글에서 다룰 더 깊은 주제입니다.
여기 거의 모든 튜토리얼이 생략하는 보안 고려 사항이 있습니다. LLM은 여러분의 데이터베이스를 직접 호출할 수 없습니다. HTTP 요청을 보낼 수도 없습니다. 임의의 코드를 실행할 수도 없습니다. 모든 도구 실행은 여러분의 Spring 코드를 거칩니다. 이는 모든 도구 실행이 시스템의 다른 함수 호출과 마찬가지로 인증(authenticated), 속도 제한(rate-limited), 로깅(logged) 및 검증(validated)될 수 있음을 의미합니다.
이것은 제약 사항이 아니라 설계입니다. 인프라의 열쇠를 통째로 넘겨주지 않고도 LLM에 강력한 기능을 안전하게 노출할 수 있습니다.
도구 호출(tool calling)을 사용하지 말아야 할 때
LLM이 자신의 학습 데이터만으로 답변할 수 있는 질문에는 도구 호출을 사용하지 마세요. 모든 도구 호출은 왕복 시간(round trip)을 추가합니다. 즉, 애플리케이션이 LLM의 응답을 기다리고, 함수를 실행한 뒤, 다시 두 번째 요청을 보내야 합니다. 이는 실제 지연 시간(latency)으로 이어집니다. 만약 누군가 "P/E 비율(주가수익비율)이 무엇인지 설명해줘"라고 묻는다면, 금융 API를 호출할 필요가 없습니다. 도구 호출은 모델이 진정으로 가질 수 없는 데이터를 위해서만 남겨두세요.
순환적인 도구 체인(circular tool chains)을 주의하세요. 만약 도구 A가 도구 B를 트리거할 수 있고, 도구 B가 특정 조건에서 다시 도구 A를 트리거할 수 있다면, 결국 루프(loop)에 빠질 수 있습니다. Spring AI는 이러한 사이클을 자동으로 끊어주지 않습니다. 이를 방지하기 위해 도구의 책임을 명확하고 좁게 정의하세요.
설명 드리프트(Description drift)는 실제 유지보수 시 발생하는 문제입니다. 기반이 되는 함수가 변경될 때 — 즉, 새로운 파라미터가 추가되거나, 로직이 변경되거나, 반환 형식이 달라질 때 — @Tool 설명을 그에 맞춰 업데이트해야 합니다. LLM은 코드가 아니라 설명을 바탕으로 추론합니다. 오래된(stale) 설명은 잘못된 인자(arguments) 전달과 호출 실패로 이어집니다.
지연 시간(Latency)은 단계(hop)가 거듭될수록 누적됩니다. 도구 호출 한 번은 대략 500ms1s를 추가합니다. 세 번의 도구 호출이 순차적으로 일어나면 23초가 추가될 수 있습니다. 사용자 대상 챗봇(chatbot)의 경우 이는 체감될 정도의 시간입니다. 도구를 조합 가능(composable)하게 설계하되, 유스케이스(use case)가 진정으로 요구하지 않는 한 깊게 체이닝(chained)되지 않도록 설계하세요.
Spring AI 2.x — 업그레이드 시 주의사항
만약 2025년 중반 이전에 작성된 도구 호출(tool-calling) 튜토리얼을 따라하고 있는데 코드가 작동하지 않는다면, 아마도 이 이유 때문일 것입니다.
과거 방식은 FunctionCallback과 장황한 빈(bean) 등록, 긴 설정 체인, 수동 입력/출력 타입 정의, 그리고 FunctionCallbackWrapper 설정을 사용했습니다. Spring AI는 이 모든 방식에서 벗어났습니다.
기존의 .tools("toolName") 구문은 Spring AI 1.0 GA에서 더 이상 안정적으로 작동하지 않습니다. 만약 도구가 인식되지 않는다면, 문자열 이름이 아니라 빈을 defaultTools(myToolBean)에 직접 전달하고 있는지 확인하세요.
이 글에서 보여주는 새로운 어노테이션(annotation) 기반 방식이 현재 권장되는 경로입니다. 이 방식은 더 깔끔하고, 수동 스키마(schema) 정의가 필요 없으며, Spring Boot 빈이 작동하는 방식에 자연스럽게 부합합니다. 이전 버전에서 마이그레이션하는 경우, 변경 사항은 대부분 기계적입니다. FunctionCallback 등록을 @Tool이 붙은 메서드로 교체하고 ChatClient 설정을 업데이트하면 됩니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기