
Tool Calling이란?
Spring AI 공식 문서에 따르면, Toll Calling이라고 하는 것은 AI 모델과의 요청/응답 간에 외부 API 호출 등의 추가적인 기능 지원을 위해 사용합니다.
Tool Calling의 주로 다음 두 가지로 사용된다고 합니다.
정보 추출(Information Retrieval)
DB, 검색 엔진, 파일 시스템 등과 같은 외부 소스로 부터 정보 추출을 위해 사용됩니다. 보통 주어진 질문에 대해 단순 답변을 하기 보다, 질문의 내용을 보강하기 위해 외부 소스로 부터 데이터 추출 작업을 진행하게 됩니다.
이는, LLM이 실제로 똑똑하기는 하지만 내부 지식은 학습 시점에 된 데이터를 기반으로 대답하기 때문에 "현재 서울 날씨를 알려줄래?", "현재 엔비디아의 주식이 얼마야?" 등의 질문에 대해서는 학습된 데이터가 없기 때문에 답변에 어려움이 있게 되죠.
이를 보강하기 위해, 해당 정보를 가져 오기 위한 외부 검색 도구(Tool Calling) 이 필요하게 됩니다.
보통은 RAG(Retrieval Augmented Generation)을 사용하는 시나리오에서 많이 사용되고는 합니다.
액션 수행(Taking Action)
데이터를 불러오는 정보 추출 단계와는 달리, 추출된 정보를 기반으로 실제 답변에 필요한 정보를 가지고 "데이터를 쓰거나, 실제로 일을 시키는 도구"를 수행하는 단계를 의미합니다.
예를 들면, 추출된 날씨를 기반으로 DB에 저장을 한다거나, 슬랙 알림 등의 외부 워크플로우를 실행하는 것과 같이 사용될 수 있습니다.
Tool Calling을 통한 정보 추출 예시
// 현재 timezone 기반의 날짜를 알려주는 Tool Class 작성
class DateTimeTools {
@Tool(description = "Get the current date and time in the user's timezone")
fun getCurrentDateTime(): String {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString()
}
@Tool(description = "Set a user alarm for the given time, provided in ISO-8601 format")
fun setAlarm(time: String) {
val alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME)
println("Alarm set for $alarmTime")
}
}
// 테스트 코드 작성
@SpringBootTest
class ToolCallingTest {
@DisplayName("Tool 어노테이션을 사용한 Retrieval 예제")
@Test
fun retrievalToolsTest() {
val chatClient: ChatClient = chatClientBuilder.build()
val prompt = Prompt("What day is tomorrow?")
val response = chatClient.prompt(prompt)
.tools(DateTimeTools())
.call()
.content();
println(response) // 답변 Tomorrow is October 8th, 2025.
}
@DisplayName("Tool 어노테이션을 사용한 Take Action 예제")
@Test
fun takeActionToolsTest() {
val chatClient: ChatClient = chatClientBuilder.build()
val prompt = Prompt("Can you set an alarm 10 minutes from now?")
val response = chatClient.prompt(prompt)
.tools(DateTimeTools())
.call()
.content();
println(response) // 답변 Alarm set for 2025-10-07T18:20:57
}
}
Tool Calling 흐름
Spring AI 공식 문서에 따르면, 다음과 같은 실행 흐름을 통해 정보 추출과 액션 수행이 진행되는 것을 알 수 있습니다. 아래 사진의 흐름을 간략하게 살펴보면 다음과 같습니다.
- 요청이 들어오면, AI 모델에 요청할 때 Tool에 대한 정보를 담아 보내고
- AI 모델에서는 이를 기반으로 요청에 대한 Tool 호출을 진행하게 됩니다.
- AI 모델에 의해 호출 된 Tool에서는 실행 응답을 다시 AI 모델에 내려주고
- 이를 Chat 응답으로 만들어 내려주게 됩니다.

그런데, 우리가 알고 싶은 부분은 단순히 AI 모델로 부터 Tool이 호출된다는 결과로적인 설명이 아니고, "어떻게 AI 모델에 전달"을 하고 "어떻게" AI 모델로 부터 응답을 받은 결과를 가지고 Tool Calling이 진행되는가" 라고 생각합니다.
어떻게 전달하는가?
OpenAiChatModel 클래스 내부에서는 call() 메서드가 호출되는 내부에서 다음 buildRequestPrompt() 메서드를 호출하고 있는데요,
다음 메서드 내부를 보면, prompt.getOptions()를 통해 가져온 결과를 바탕으로 tool 관련 옵션이 있을 경우, Prompt 객체를 만들 때 넣어주고 있는 것을 알 수 있습니다.
Prompt buildRequestPrompt(Prompt prompt) {
OpenAiChatOptions runtimeOptions = null;
if (prompt.getOptions() != null) {
ChatOptions var4 = prompt.getOptions();
if (var4 instanceof ToolCallingChatOptions) {
ToolCallingChatOptions toolCallingChatOptions = (ToolCallingChatOptions)var4;
runtimeOptions = (OpenAiChatOptions)ModelOptionsUtils.copyToTarget(toolCallingChatOptions, ToolCallingChatOptions.class, OpenAiChatOptions.class);
} else {
runtimeOptions = (OpenAiChatOptions)ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class, OpenAiChatOptions.class);
}
}
OpenAiChatOptions requestOptions = (OpenAiChatOptions)ModelOptionsUtils.merge(runtimeOptions, this.defaultOptions, OpenAiChatOptions.class);
if (runtimeOptions != null) {
if (runtimeOptions.getTopK() != null) {
logger.warn("The topK option is not supported by OpenAI chat models. Ignoring.");
}
requestOptions.setHttpHeaders(this.mergeHttpHeaders(runtimeOptions.getHttpHeaders(), this.defaultOptions.getHttpHeaders()));
requestOptions.setInternalToolExecutionEnabled((Boolean)ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled()));
requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames()));
requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), this.defaultOptions.getToolCallbacks()));
requestOptions.setToolContext(ToolCallingChatOptions.mergeToolContext(runtimeOptions.getToolContext(), this.defaultOptions.getToolContext()));
} else {
requestOptions.setHttpHeaders(this.defaultOptions.getHttpHeaders());
requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled());
requestOptions.setToolNames(this.defaultOptions.getToolNames());
requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks());
requestOptions.setToolContext(this.defaultOptions.getToolContext());
}
ToolCallingChatOptions.validateToolCallbacks(requestOptions.getToolCallbacks());
return new Prompt(prompt.getInstructions(), requestOptions);
}
해당 Prompt에 담긴 ToolCall 객체는 다음의 internalCall() 메서드 내부에서 OpenAI 모델 요청 Body에 담겨지게 됩니다.
public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) {
OpenAiApi.ChatCompletionRequest request = this.createRequest(prompt, false);
ChatModelObservationContext observationContext = ChatModelObservationContext.builder().prompt(prompt).provider(OpenAiApiConstants.PROVIDER_NAME).build();
ChatResponse response = (ChatResponse)ChatModelObservationDocumentation.CHAT_MODEL_OPERATION.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry).observe(() -> {
ResponseEntity<OpenAiApi.ChatCompletion> completionEntity = (ResponseEntity)this.retryTemplate.execute((ctx) -> this.openAiApi.chatCompletionEntity(request, this.getAdditionalHttpHeaders(prompt))); // 요청부에 prompt 객체가 담긴다.
...
});
...
}
어떻게 전달받아, Tool Calling을 하는가?
Tool Calling을 수행하는 로직은 ChatModel 구현체 내부에서 모델의 응답을 받는 곳에서 수행하게 되는데요,
저는 OpenAI 모델을 사용하고 있기 때문에 OpenAiChatModel 클래스를 기반으로 설명을 진행하겠습니다.
OpenAiChatModel 내부에서는 ChatClient의 call() 메서드가 호출되는 내부에서 발생하게 됩니다.
다음 코드의 internalCall() 내부를 보면 this.toolExecutionEligibilityPredicate.isToolExecutionRequired() 메서드로 Tool Calling을 수행할지 체크하고 있는 것을 확인할 수 있습니다.
해당 로직은 ChatResponse의 generations 속성 아래, toolCalls가 존재하는지를 통해 Tool Calling을 수행할지를 체크합니다.
public ChatResponse call(Prompt prompt) {
Prompt requestPrompt = this.buildRequestPrompt(prompt);
return this.internalCall(requestPrompt, (ChatResponse)null);
}
public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) {
...
if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) {
ToolExecutionResult toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response);
return toolExecutionResult.returnDirect() ? ChatResponse.builder().from(response).generations(ToolExecutionResult.buildGenerations(toolExecutionResult)).build() : this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), response);
} else {
return response;
}
}
Tool Calling을 수행할 수 있는 상태라면, toolCallingManager.executeToolCalls()를 통해 실제 Tool Calling을 수행하게 되는데요, 다음 DefaultToolCallingManager 클래스 내에 executeToolCall() 메서드 내부를 보면, 현재 ChatResponse의 AssistantMessage 내에 존재하는 toolName을 가지고 현재 ChatClient를 만들면서 전달한 ToolCallback 객체들을 순회하며 일치하는 Tool Calling을 수행하게 됩니다.
private InternalToolExecutionResult executeToolCall(Prompt prompt, AssistantMessage assistantMessage, ToolContext toolContext) {
List<ToolCallback> toolCallbacks = List.of();
if (returnDirect instanceof ToolCallingChatOptions toolCallingChatOptions) {
toolCallbacks = toolCallingChatOptions.getToolCallbacks();
}
...
for(AssistantMessage.ToolCall toolCall : assistantMessage.getToolCalls()) {
...
String toolName = toolCall.name();
String toolInputArguments = toolCall.arguments();
ToolCallback toolCallback = (ToolCallback)toolCallbacks.stream().filter((tool) -> toolName.equals(tool.getToolDefinition().name())).findFirst().orElseGet(() -> this.toolCallbackResolver.resolve(toolName));
...
}
return new InternalToolExecutionResult(new ToolResponseMessage(toolResponses, Map.of()), returnDirect);
}
정리
Spring AI에서 제공하는 Tool Calling에 대한 이해 및 동적 수행에 대해 살펴봤는데요,
이제 Tool Calling에서 제공하는 기능들은 어떤 것들이 있는지 다음 글에서 살펴보려고 합니다.
참고
https://docs.spring.io/spring-ai/reference/api/tools.html
Tool Calling :: Spring AI Reference
Spring AI supports tool calling through a set of flexible abstractions that allow you to define, resolve, and execute tools in a consistent way. This section provides an overview of the main concepts and components of tool calling in Spring AI. When we wan
docs.spring.io
'Spring Framework > AI' 카테고리의 다른 글
| Spring AI - Tool Calling(2): ToolCallback에 대한 이해 (0) | 2025.10.08 |
|---|---|
| Spring AI - Chat memory (0) | 2025.10.02 |
| Spring AI - Advisor API (0) | 2025.09.30 |
| Spring AI - Structured Output Converter (0) | 2025.09.27 |
| Spring AI - ChatClient API 살펴보기 (0) | 2025.09.21 |