Spring AI

Tool Calling(함수 호출)

LLM은 학습 시점 이후의 일이나, 우리 DB·외부 API 같은 실시간·사적 정보를 모른다. Tool Calling은 LLM이 답하기 위해 필요할 때 우리가 만든 Java 메서드를 직접 호출하게 하는 기능이다. "지금 몇 시야?", "1002번 주문 어디까지 왔어?" 같은 질문을 실제 데이터로 답하게 만든다.

이 편에서 만드는 것

@Tool을 단 도구 두 개(현재 시각, 주문 조회)와 이를 물려 호출하는 /tool/ask 엔드포인트.


1. 동작 원리

LLM은 도구를 직접 실행하지 않는다. 대신 "이 도구를 이런 인자로 불러 줘"라고 요청하고, Spring AI가 실제 메서드를 호출한 뒤 그 결과를 다시 LLM에 돌려준다. LLM은 그 결과로 최종 답을 만든다.

sequenceDiagram
    autonumber
    participant U as 사용자
    participant LLM
    participant App as Spring AI
    participant T as @Tool 메서드
    U->>LLM: "1002번 주문 어디까지 왔어?"
    LLM->>App: getOrderStatus("1002") 호출 요청
    App->>T: 실제 메서드 실행
    T-->>App: "배송 중 (오늘 도착 예정)"
    App-->>LLM: 도구 결과 전달
    LLM-->>U: "1002번은 배송 중이며 오늘 도착 예정입니다."

2. 도구 정의 — @Tool

@Component 빈의 메서드에 @Tool을 달고, description으로 언제 쓰는 도구인지 설명한다. 이 설명을 보고 LLM이 호출 여부를 판단하므로 명확히 적는다.

@Component
public class DateTimeTools {

    @Tool(description = "현재 날짜와 시간을 알려준다. 시간 관련 질문에 사용한다.")
    public String getCurrentDateTime() {
        return LocalDateTime.now()
                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss (E)"));
    }
}

파라미터를 받는 도구는 @ToolParam으로 각 인자의 의미를 설명한다. LLM이 사용자 문장에서 값을 뽑아 채워 준다.

@Component
public class OrderTools {

    private static final Map<String, String> ORDERS = Map.of(
            "1001", "배송 완료 (2026-05-28 수령)",
            "1002", "배송 중 (오늘 도착 예정)",
            "1003", "상품 준비 중");

    @Tool(description = "주문번호로 배송 상태를 조회한다.")
    public String getOrderStatus(
            @ToolParam(description = "조회할 주문번호 (예: 1002)") String orderId) {
        return ORDERS.getOrDefault(orderId, "해당 주문번호를 찾을 수 없습니다.");
    }
}

description이 곧 성능

도구·파라미터 설명이 부실하면 LLM이 엉뚱한 도구를 부르거나 인자를 잘못 채운다. "무엇을, 언제, 어떤 형식으로"를 사람이 읽듯 자연스럽게 적자.


3. 도구 물려주기 — .tools(...)

ChatClient 호출에 .tools(...)로 사용 가능한 도구들을 넘긴다. 나머지(언제 부를지, 인자 추출, 결과 반영)는 Spring AI가 자동 처리한다.

@GetMapping("/tool/ask")
public String ask(@RequestParam String message) {
    return chatClient.prompt()
            .user(message)
            .tools(dateTimeTools, orderTools)   // ← 사용 가능한 도구 목록
            .call()
            .content();
}
curl -G "http://localhost:8080/tool/ask" --data-urlencode "message=지금 몇 시야?"
# 13시 30분입니다.

curl -G "http://localhost:8080/tool/ask" --data-urlencode "message=1002번 주문 어디까지 왔어?"
# 1002번 주문은 현재 배송 중이며, 오늘 도착할 예정입니다.

curl -G "http://localhost:8080/tool/ask" --data-urlencode "message=9999번 주문 상태 알려줘"
# 9999번 주문에 대한 정보가 없습니다. 주문번호를 다시 확인해 주세요.

LLM은 질문에 따라 필요한 도구만 골라 호출한다. 시간 질문엔 getCurrentDateTime, 주문 질문엔 getOrderStatus를, 없는 주문번호엔 도구가 돌려준 "찾을 수 없음"을 그대로 반영해 자연스럽게 답한다.


4. 실무 활용

도구 안에서는 평범한 자바 코드를 그대로 쓸 수 있다 — Repository로 DB를 조회하고, WebClient로 외부 API를 부르고, 장비를 제어한다.

@Component
@RequiredArgsConstructor
public class WeatherTools {
    private final WeatherApiClient api;   // 다른 빈 주입도 자유롭게

    @Tool(description = "도시의 현재 날씨를 조회한다.")
    public String getWeather(@ToolParam(description = "도시명") String city) {
        return api.fetch(city);   // 실제 외부 API 호출
    }
}

도구는 부수효과에 주의

LLM이 부르는 도구가 데이터를 변경(주문 취소, 결제, 파일 삭제)한다면, 호출 전 검증·권한 확인·확인 절차를 반드시 둬야 한다. 조회성 도구부터 시작하는 것이 안전하다.


정리

  • @Tool + @ToolParam으로 메서드를 도구로 노출한다 — 설명이 핵심.

  • .tools(...)로 물려주면 LLM이 알아서 고르고, 인자를 채우고, 결과를 반영한다.

  • 도구 안에서는 DB·API·장비 등 무엇이든 호출할 수 있어, LLM을 실제 시스템과 연결하는 다리가 된다.

➡️ 다음 편: RAG

마지막 수정 2026년 10월 22일