์๋ ํ์ธ์ dev_writer์ ๋๋ค.
์ด๋ฒ ์๊ฐ์๋ Spring AI ๊ณต์ ๋ฌธ์ ์ค Tool Calling์ ๋ํด ๋ฒ์ญํ ๋ด์ฉ์ ์ ๋ฌํด ๋๋ฆฌ๊ฒ ์ต๋๋ค.
Tool Calling
Tool calling(ํด ํธ์ถ, ํน์ ํจ์ ํธ์ถ์ด๋ผ๊ณ ๋ ํจ)์ AI ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ชจ๋ธ์ด API๋ ๋๊ตฌ ์งํฉ๊ณผ ์ํธ์์ฉํ ์ ์๋๋ก ํ๋ ์ผ๋ฐ์ ์ธ ํจํด์ผ๋ก, ๋ชจ๋ธ์ ๊ธฐ๋ฅ์ ํ์ฅํ ์ ์๋๋ก ํฉ๋๋ค. ๋๊ตฌ๋ ์ฃผ๋ก ๋ค์๊ณผ ๊ฐ์ ๋ชฉ์ ์ ์ฌ์ฉ๋ฉ๋๋ค:
1. ์ ๋ณด ๊ฒ์(Information Retrieval)
์ด ๋ฒ์ฃผ์ ๋๊ตฌ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค, ์น ์๋น์ค, ํ์ผ ์์คํ ๋๋ ์น ๊ฒ์ ์์ง๊ณผ ๊ฐ์ ์ธ๋ถ ์์ค์์ ์ ๋ณด๋ฅผ ๊ฒ์ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ๋ชฉํ๋ ๋ชจ๋ธ์ ์ง์์ ์ฆ๊ฐํ์ฌ, ๊ทธ๋ ์ง ์์ผ๋ฉด ๋ตํ ์ ์๋ ์ง๋ฌธ์ ๋ตํ ์ ์๋๋ก ํ๋ ๊ฒ์ ๋๋ค. ์๋ฅผ ๋ค์ด, ์ฃผ์ด์ง ์์น์ ํ์ฌ ๋ ์จ๋ฅผ ์กฐํํ๊ฑฐ๋, ์ต์ ๋ด์ค ๊ธฐ์ฌ๋ฅผ ๊ฒ์ํ๊ฑฐ๋, ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ํน์ ๋ ์ฝ๋๋ฅผ ์กฐํํ๋ ๋ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด๋ฌํ ํ์ฉ์ RAG (Retrieval Augmented Generation) ์๋๋ฆฌ์ค์์ ํ์ฉ๋ ์ ์์ต๋๋ค.
2. ์์ ์ํ(Taking Action)
์ด ๋ฒ์ฃผ์ ๋๊ตฌ๋ ์ด๋ฉ์ผ ์ ์ก, ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๋ ์ฝ๋ ์์ฑ, ์์ ์ ์ถ, ์ํฌํ๋ก์ฐ ํธ๋ฆฌ๊ฑฐ ๋ฑ๊ณผ ๊ฐ์ด ์ํํธ์จ์ด ์์คํ ์์ ์์ ์ ์ํํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ๋ชฉํ๋ ์ฌ๋์ด ์ง์ ์ํํ๊ฑฐ๋ ๋ช ์์ ์ผ๋ก ํ๋ก๊ทธ๋๋ฐํด์ผ ํ๋ ์์ ์ ์๋ํํ๋ ๊ฒ์ ๋๋ค. ์๋ฅผ ๋ค์ด, ์ฑ๋ด๊ณผ ์ํธ์์ฉํ๋ ๊ณ ๊ฐ์ ์ํด ํญ๊ณตํธ์ ์์ฝํ๊ฑฐ๋, ์นํ์ด์ง์ ์์์ ์๋์ผ๋ก ์์ฑํ๊ฑฐ๋, ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ(TDD) ์๋๋ฆฌ์ค์์ ์๋ํ๋ ํ ์คํธ ๊ธฐ๋ฐ์ผ๋ก Java ํด๋์ค๋ฅผ ๊ตฌํํ ์ ์์ต๋๋ค.
ํด ํธ์ถ์ ์ผ๋ฐ์ ์ผ๋ก ๋ชจ๋ธ์ ๊ธฐ๋ฅ์ผ๋ก ๊ฐ์ฃผ๋์ง๋ง, ์ค์ ๋ก๋ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋๊ตฌ ํธ์ถ ๋ก์ง์ ์ ๊ณตํด์ผ ํฉ๋๋ค. ๋ชจ๋ธ์ ๋จ์ง ๋๊ตฌ ํธ์ถ ์์ฒญ๊ณผ ์ ๋ ฅ ์ธ์(arguments)๋ฅผ ์ ๊ณตํ ์ ์์ ๋ฟ์ด๋ฉฐ, ๋๊ตฌ ํธ์ถ์ ์คํํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ ์ญํ ์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ํํฉ๋๋ค. ๋ชจ๋ธ์ ๋๊ตฌ๋ก ์ ๊ณต๋ API์ ์ ๊ทผํ ์ ์์ต๋๋ค. ์ด๋ ๋ณด์ ์ธก๋ฉด์์ ๋งค์ฐ ์ค์ํ ๊ณ ๋ ค ์ฌํญ์ ๋๋ค.
Spring AI๋ ๋๊ตฌ๋ฅผ ์ ์ํ๊ณ , ๋ชจ๋ธ์ ๋๊ตฌ ํธ์ถ ์์ฒญ์ ํด๊ฒฐํ๊ณ , ๋๊ตฌ ํธ์ถ์ ์คํํ๋ ๋ฐ ์ ์ฉํ API๋ค์ ์ ๊ณตํฉ๋๋ค. ๋ค์ ์น์ ์์๋ Spring AI์ ๋๊ตฌ ํธ์ถ ๊ธฐ๋ฅ์ ๋ํ ๊ฐ์๋ฅผ ์ ๊ณตํฉ๋๋ค.
์ฐธ๊ณ
์ด๋ค AI ๋ชจ๋ธ์ด ๋๊ตฌ ํธ์ถ์ ์ง์ํ๋์ง ํ์ธํ๋ ค๋ฉด [Chat Model Comparisons]๋ฅผ ์ฐธ์กฐํ์ธ์.
ํ
๋ ์ด์ ์ฌ์ฉ๋์ง ์๋ FunctionCallback์์ ToolCallback API๋ก์ ๋ง์ด๊ทธ๋ ์ด๋ ๊ฐ์ด๋๋ฅผ ๋ฐ๋ฅด์ธ์.
๋น ๋ฅธ ์์
Spring AI์์ tool calling์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด๊ฒ ์ต๋๋ค. ์ฌ๊ธฐ์๋ ๋ ๊ฐ์ง ๊ฐ๋จํ ๋๊ตฌ๋ฅผ ๊ตฌํํด ๋ด ๋๋ค: ํ๋๋ ์ ๋ณด ๊ฒ์์ฉ, ๋ค๋ฅธ ํ๋๋ ์์ ์ํ์ฉ์ ๋๋ค. ์ ๋ณด ๊ฒ์ ๋๊ตฌ๋ ์ฌ์ฉ์์ ์๊ฐ๋์ ๋ง๋ ํ์ฌ ๋ ์ง์ ์๊ฐ์ ๊ฐ์ ธ์ค๋ ๊ธฐ๋ฅ์ด๊ณ , ์์ ์ํ ๋๊ตฌ๋ ์ง์ ๋ ์๊ฐ์ ์๋์ ์ค์ ํ๋ ๊ธฐ๋ฅ์ ๋๋ค.
์ ๋ณด ๊ฒ์ ๋๊ตฌ (Information Retrieval)
AI ๋ชจ๋ธ์ ์ค์๊ฐ ์ ๋ณด์ ์ ๊ทผํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ “ํ์ฌ ๋ ์ง”๋ “๋ ์จ ์๋ณด”์ฒ๋ผ ์ค์๊ฐ ์ ๋ณด์ ๋ํ ์ง๋ฌธ์ ๋ชจ๋ธ์ด ์ง์ ๋ตํ ์ ์์ต๋๋ค. ํ์ง๋ง ์ฐ๋ฆฌ๋ ์ด๋ฐ ์ ๋ณด๋ฅผ ๊ฒ์ํด ์ฃผ๋ ๋๊ตฌ(tool)๋ฅผ ์ ๊ณตํจ์ผ๋ก์จ, ๋ชจ๋ธ์ด ํ์ํ ๋ ํด๋น ๋๊ตฌ๋ฅผ ํธ์ถํ๊ฒ ํ ์ ์์ต๋๋ค.
์๋๋ ์ฌ์ฉ์์ ์๊ฐ๋์ ๋ง๋ ํ์ฌ ๋ ์ง์ ์๊ฐ์ ๊ฐ์ ธ์ค๋ ๋๊ตฌ๋ฅผ DateTimeTools ํด๋์ค์ ๊ตฌํํ ์์์ ๋๋ค. ์ด ๋๊ตฌ๋ ์ ๋ ฅ ์ธ์๋ฅผ ๋ฐ์ง ์์ต๋๋ค. Spring Framework์ LocaleContextHolder๋ฅผ ์ฌ์ฉํ๋ฉด ์ฌ์ฉ์์ ์๊ฐ๋๋ฅผ ์ป์ ์ ์์ต๋๋ค. ๋๊ตฌ๋ @Tool ์ด๋ ธํ ์ด์ ์ ํตํด ์ ์๋๋ฉฐ, ๋ชจ๋ธ์ด ์ด ๋๊ตฌ๋ฅผ ์ดํดํ๊ณ ์ ์ ํ ์ฌ์ฉํ ์ ์๋๋ก ์ค๋ช ์ ํฌํจํด์ผ ํฉ๋๋ค.
import java.time.LocalDateTime;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.context.i18n.LocaleContextHolder;
class DateTimeTools {
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
์ด์ ์์์ ๋ง๋ ๋๊ตฌ๋ฅผ ๋ชจ๋ธ์ด ์ฌ์ฉํ ์ ์๋๋ก ํฉ๋๋ค. ์ด ์์์์๋ ChatClient๋ฅผ ์ฌ์ฉํ์ฌ ๋ชจ๋ธ๊ณผ ์ํธ์์ฉํฉ๋๋ค. tools() ๋ฉ์๋๋ฅผ ํตํด DateTimeTools ์ธ์คํด์ค๋ฅผ ๋ชจ๋ธ์๊ฒ ์ ๊ณตํฉ๋๋ค. ๋ชจ๋ธ์ด ํ์ฌ ๋ ์ง์ ์๊ฐ์ ์์์ผ ํ๋ ๊ฒฝ์ฐ, ๋๊ตฌ ํธ์ถ์ ์์ฒญํ๊ฒ ๋๋ฉฐ, ChatClient๋ ๋ด๋ถ์ ์ผ๋ก ํด๋น ๋๊ตฌ๋ฅผ ์คํํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋ชจ๋ธ์ ๋ฐํํฉ๋๋ค. ๋ชจ๋ธ์ ์ด ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฉํด ์ต์ข ์๋ต์ ์์ฑํฉ๋๋ค.
ChatModel chatModel = ...
String response = ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.call()
.content();
System.out.println(response);
์ถ๋ ฅ ์์๋ ๋ค์๊ณผ ๋น์ทํ ์ ์์ต๋๋ค:
Tomorrow is 2015-10-21.
์ด๋ฒ์๋ ๋๊ตฌ๋ฅผ ์ ๊ณตํ์ง ์๊ณ ๊ฐ์ ์ง๋ฌธ์ ํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ ์๋ต์ด ๋์ต๋๋ค:
I am an AI and do not have access to real-time information. Please provide the current date so I can accurately determine what day tomorrow will be.
์ฆ, ๋๊ตฌ ์์ด ๋ชจ๋ธ์ ํ์ฌ ๋ ์ง์ ์๊ฐ์ ์ ์ ์๊ธฐ ๋๋ฌธ์ ์ง๋ฌธ์ ์ ํํ ๋ต๋ณํ ์ ์์ต๋๋ค.
์์ ์ํ (Taking Actions)
AI ๋ชจ๋ธ์ ํน์ ๋ชฉํ๋ฅผ ๋ฌ์ฑํ๊ธฐ ์ํ ๊ณํ์ ์์ฑํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๋ด๋งํฌ ์ฌํ์ ์์ฝํ๋ ๊ณํ์ ์ธ์ธ ์๋ ์์ง๋ง, ๊ทธ ๊ณํ์ ์ค์ ๋ก ์คํํ๋ ๋ฅ๋ ฅ์ ์์ต๋๋ค. ์ด๋ ๋๊ตฌ(tool)๊ฐ ์ฌ์ฉ๋ฉ๋๋ค. ๋๊ตฌ๋ ๋ชจ๋ธ์ด ์์ฑํ ๊ณํ์ ์คํํ๋ ๋ฐ ํ์ฉ๋ฉ๋๋ค. ์์ ์์ ์์๋ ํ์ฌ ๋ ์ง์ ์๊ฐ์ ๊ตฌํ๋ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์ต๋๋ค. ์ด๋ฒ ์์ ์์๋ ํน์ ์๊ฐ์ ์๋์ ์ค์ ํ๋ ๋๊ตฌ๋ฅผ ์ ์ํฉ๋๋ค. ๋ชฉํ๋ “์ง๊ธ๋ถํฐ 10๋ถ ํ์ ์๋์ ์ค์ ํ๋ ๊ฒ”์ด๋ฉฐ, ์ด๋ฅผ ์ํด ๋ ๊ฐ์ง ๋๊ตฌ(ํ์ฌ ์๊ฐ ์กฐํ + ์๋ ์ค์ )๋ฅผ ๋ชจ๋ ๋ชจ๋ธ์ ์ ๊ณตํด์ผ ํฉ๋๋ค.
๊ธฐ์กด๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก DateTimeTools ํด๋์ค์ ์๋ก์ด ๋๊ตฌ ๋ฉ์๋๋ฅผ ์ถ๊ฐํฉ๋๋ค. ์ด ๋๊ตฌ๋ ISO-8601 ํ์์ ์๊ฐ ๋ฌธ์์ด์ ์ธ์๋ก ๋ฐ์ ์ฝ์์ ์๋ ์ค์ ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํฉ๋๋ค. ์ญ์ @Tool ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ์ฌ ๋๊ตฌ๋ก ์ ์ํ๊ณ , ๋ชจ๋ธ์ด ์ดํดํ ์ ์๋๋ก ์์ธ ์ค๋ช ๋ ํฌํจํฉ๋๋ค.
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.context.i18n.LocaleContextHolder;
class DateTimeTools {
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
@Tool(description = "Set a user alarm for the given time, provided in ISO-8601 format")
void setAlarm(String time) {
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
System.out.println("Alarm set for " + alarmTime);
}
}
๋ค์์ผ๋ก ๋ ๊ฐ์ ๋๊ตฌ๋ฅผ ๋ชจ๋ธ์์ ์ฌ์ฉํ ์ ์๋๋ก ํฉ๋๋ค. ๋ชจ๋ธ๊ณผ ์ํธ์์ฉํ๊ธฐ ์ํด ChatClient๋ฅผ ์ฌ์ฉํฉ๋๋ค. tools() ๋ฉ์๋๋ฅผ ํตํด DateTimeTools ์ธ์คํด์ค๋ฅผ ๋ชจ๋ธ์ ์ ๋ฌํฉ๋๋ค. “10๋ถ ํ์ ์๋ ์ค์ ํด ์ค”๋ผ๊ณ ์์ฒญํ๋ฉด, ๋ชจ๋ธ์ ๋จผ์ ํ์ฌ ๋ ์ง์ ์๊ฐ์ ์์์ผ ํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ํ์ฌ ๋ ์ง์ ์๊ฐ์ ์ฌ์ฉํด ์๋ ์๊ฐ์ ๊ณ์ฐํฉ๋๋ค. ๋ง์ง๋ง์ผ๋ก ์๋ ๋๊ตฌ๋ฅผ ์ฌ์ฉํด ์๋์ ์ค์ ํฉ๋๋ค. ๋ด๋ถ์ ์ผ๋ก ChatClient๋ ๋ชจ๋ธ๋ก๋ถํฐ์ ๋ชจ๋ ๋๊ตฌ ํธ์ถ ์์ฒญ์ ์ฒ๋ฆฌํ๊ณ ๋๊ตฌ ํธ์ถ ์คํ ๊ฒฐ๊ณผ๋ฅผ ๋ชจ๋ธ์ ๋ค์ ์ ๋ฌํ์ฌ, ๋ชจ๋ธ์ด ์ต์ข ์๋ต์ ์์ฑํ ์ ์๋๋ก ํฉ๋๋ค.
ChatModel chatModel = ...
String response = ChatClient.create(chatModel)
.prompt("Can you set an alarm 10 minutes from now?")
.tools(new DateTimeTools())
.call()
.content();
System.out.println(response);
์ ํ๋ฆฌ์ผ์ด์ ๋ก๊ทธ๋ฅผ ํ์ธํ๋ฉด, ์๋์ด ์ ํํ ์๊ฐ์ ์ค์ ๋์๋์ง ํ์ธํ ์ ์์ต๋๋ค.
๊ฐ์
Spring AI๋ ๋๊ตฌ๋ฅผ ์ ์ํ๊ณ , ํด์ํ๊ณ , ์คํํ ์ ์๋ ์ ์ฐํ ์ถ์ํ ์งํฉ์ ํตํด tool calling์ ์ง์ํฉ๋๋ค. ์ด ์น์ ์์๋ Spring AI์์ tool calling๊ณผ ๊ด๋ จ๋ ์ฃผ์ ๊ฐ๋ ๊ณผ ๊ตฌ์ฑ ์์์ ๋ํ ๊ฐ์๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ๋ชจ๋ธ์ด ๋๊ตฌ๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ํ๋ ค๋ฉด, ํด๋น ๋๊ตฌ์ ์ ์๋ฅผ ์ฑํ ์์ฒญ์ ํฌํจ์์ผ์ผ ํฉ๋๋ค. ๊ฐ ๋๊ตฌ ์ ์๋ ์ด๋ฆ, ์ค๋ช , ์ ๋ ฅ ํ๋ผ๋ฏธํฐ์ ์คํค๋ง๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
- ๋ชจ๋ธ์ด ๋๊ตฌ๋ฅผ ํธ์ถํ๊ธฐ๋ก ๊ฒฐ์ ํ๋ฉด, ๋๊ตฌ ์ด๋ฆ๊ณผ ์ ์๋ ์คํค๋ง์ ๋ฐ๋ฅธ ์ ๋ ฅ ํ๋ผ๋ฏธํฐ๋ฅผ ํฌํจํ ์๋ต์ ๋ณด๋ ๋๋ค.
- ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๊ตฌ ์ด๋ฆ์ ์ฌ์ฉํด ์ด๋ค ๋๊ตฌ์ธ์ง ์๋ณํ๊ณ , ์ ๊ณต๋ ์ ๋ ฅ ํ๋ผ๋ฏธํฐ๋ก ๋๊ตฌ๋ฅผ ์คํํ๋ ์ฑ ์์ ์ง๋๋ค.
- ๋๊ตฌ ํธ์ถ์ ๊ฒฐ๊ณผ๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฒ๋ฆฌ๋ฉ๋๋ค.
- ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๊ตฌ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๋ชจ๋ธ์๊ฒ ๋ค์ ์ ๋ฌํฉ๋๋ค.
- ๋ชจ๋ธ์ ๋๊ตฌ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ์ถ๊ฐ์ ์ธ ๋ฌธ๋งฅ์ผ๋ก ํ์ฉํ์ฌ ์ต์ข ์๋ต์ ์์ฑํฉ๋๋ค.
๋๊ตฌ(Tools)๋ tool calling์ ๊ตฌ์ฑ๋จ์์ด๋ฉฐ, ToolCallback ์ธํฐํ์ด์ค๋ก ๋ชจ๋ธ๋ง ๋ฉ๋๋ค. Spring AI๋ ๋ฉ์๋๋ ํจ์๋ก๋ถํฐ ToolCallback์ ์ง์ ํ ์ ์๋ ๊ธฐ๋ณธ ์ง์์ ์ ๊ณตํ์ง๋ง, ๋ ๋ค์ํ ์ฌ์ฉ ์ฌ๋ก๋ฅผ ์ํด ์ง์ ToolCallback ๊ตฌํ์ฒด๋ฅผ ์ ์ํ ์๋ ์์ต๋๋ค.
ChatModel ๊ตฌํ์ฒด๋ ๋๊ตฌ ํธ์ถ ์์ฒญ์ ํด๋น ToolCallback ๊ตฌํ์ฒด๋ก ์๋์ผ๋ก ์ ๋ฌํ๋ฉฐ, ๋๊ตฌ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๋ค์ ๋ชจ๋ธ์ ์ ๋ฌํฉ๋๋ค. ์ต์ข ์๋ต์ ์ด ๊ฒฐ๊ณผ๋ฅผ ๋ฐํ์ผ๋ก ๋ชจ๋ธ์ด ์์ฑํฉ๋๋ค. ์ด๋ฌํ ๊ณผ์ ์ ๋๊ตฌ ์คํ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ด๋ฆฌํ๋ ToolCallingManager ์ธํฐํ์ด์ค๋ฅผ ํตํด ์ด๋ฃจ์ด์ง๋๋ค.
ChatClient์ ChatModel ๋ชจ๋ ๋ชจ๋ธ์ด ์ฌ์ฉํ ์ ์๋๋ก ๋๊ตฌ๋ฅผ ์ง์ ํ๊ธฐ ์ํด ToolCallback ๊ฐ์ฒด ๋ฆฌ์คํธ์, ์ด๋ฅผ ์ค์ ๋ก ์คํํ ToolCallingManager๋ฅผ ์ ๋ฌ๋ฐ์ต๋๋ค.
ToolCallback ๊ฐ์ฒด๋ฅผ ์ง์ ์ ๋ฌํ๋ ๊ฒ ์ธ์๋, ๋๊ตฌ ์ด๋ฆ ๋ชฉ๋ก์ ์ ๋ฌํ ์๋ ์์ผ๋ฉฐ, ์ด ๊ฒฝ์ฐ์๋ ToolCallbackResolver ์ธํฐํ์ด์ค๋ฅผ ํตํด ๋์ ์ผ๋ก ํด๋น ๋๊ตฌ๊ฐ ํด์๋ฉ๋๋ค.
๋ค์ ์น์ ์์๋ ์ด๋ฌํ ๊ฐ๋ ๋ค๊ณผ API์ ๋ํด ๋ ์์ธํ ์ค๋ช ํ๋ฉฐ, ์ฌ์ฉ์ ์ ์ ๋ฐ ํ์ฅ ๋ฐฉ๋ฒ๋ ํจ๊ป ๋ค๋ฃฐ ์์ ์ ๋๋ค.
๋ฉ์๋๋ฅผ ๋๊ตฌ๋ก ์ฌ์ฉํ๊ธฐ
Spring AI๋ ๋ฉ์๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋๊ตฌ(ToolCallback)๋ฅผ ์ง์ ํ๋ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ง์ํฉ๋๋ค:
- ์ ์ธ์ ์ผ๋ก ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ: @Tool ์ด๋ ธํ ์ด์ ์ฌ์ฉ
- ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ: ์ ์์ค์ MethodToolCallback ๊ตฌํ ์ฌ์ฉ
์ ์ธ์ ์ง์ : @Tool
๋ฉ์๋์ @Tool ์ด๋ ธํ ์ด์ ์ ๋ถ์ด๋ฉด, ํด๋น ๋ฉ์๋๋ฅผ ๋๊ตฌ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
class DateTimeTools {
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
@Tool ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ฉด ๋๊ตฌ์ ๋ํ ํต์ฌ ์ ๋ณด๋ฅผ ์ ๊ณตํ ์ ์์ต๋๋ค:
- name: ๋๊ตฌ์ ์ด๋ฆ์ ๋๋ค. ์ง์ ํ์ง ์์ผ๋ฉด ๋ฉ์๋ ์ด๋ฆ์ด ์ฌ์ฉ๋ฉ๋๋ค. AI ๋ชจ๋ธ์ ์ด ์ด๋ฆ์ ๊ธฐ๋ฐ์ผ๋ก ๋๊ตฌ๋ฅผ ์๋ณํ์ฌ ํธ์ถํ๊ธฐ ๋๋ฌธ์, ๊ฐ์ ํด๋์ค ๋ด์์ ์ค๋ณต๋ ์ด๋ฆ์ ๋๊ตฌ๋ ํ์ฉ๋์ง ์์ต๋๋ค. ํ๋์ ์ฑํ ์์ฒญ์ ๋ํด ๋ชจ๋ธ์ด ์ฌ์ฉํ ์ ์๋ ๋๊ตฌ ์ ์ฒด์์ ์ด๋ฆ์ ๊ณ ์ ํด์ผ ํฉ๋๋ค.
- description: ๋๊ตฌ์ ๋ํ ์ค๋ช ์ผ๋ก, ๋ชจ๋ธ์ด ๋๊ตฌ๋ฅผ ์ธ์ , ์ด๋ป๊ฒ ํธ์ถํ ์ง๋ฅผ ์ดํดํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ์ง์ ํ์ง ์์ผ๋ฉด ๋ฉ์๋ ์ด๋ฆ์ด ์ค๋ช ์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค. ํ์ง๋ง ๋ชจ๋ธ์ด ๋๊ตฌ์ ๋ชฉ์ ๊ณผ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ์ ํํ ์ดํดํ๋ ค๋ฉด ์์ธํ ์ค๋ช ์ ๋ฐ๋์ ์ ๊ณตํ๋ ๊ฒ์ด ๊ถ์ฅ๋ฉ๋๋ค. ์ค๋ช ์ด ๋ถ์กฑํ ๊ฒฝ์ฐ, ๋ชจ๋ธ์ด ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์ง ์๊ฑฐ๋ ์๋ชป ์ฌ์ฉํ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
- returnDirect: ๋๊ตฌ์ ์คํ ๊ฒฐ๊ณผ๋ฅผ ๋ชจ๋ธ์ด ์๋ ํด๋ผ์ด์ธํธ์ ์ง์ ๋ฐํํ ์ง ์ฌ๋ถ์ ๋๋ค. ์์ธํ ๋ด์ฉ์ Return Direct ํญ๋ชฉ์ ์ฐธ์กฐํ์ธ์.
- resultConverter: ๋๊ตฌ ์คํ ๊ฒฐ๊ณผ๋ฅผ ๋ฌธ์์ด(String)๋ก ๋ณํํ์ฌ AI ๋ชจ๋ธ์ ์ ๋ฌํ ๋ ์ฌ์ฉํ๋ ToolCallResultConverter ๊ตฌํ์ฒด์ ๋๋ค. ์์ธํ ๋ด์ฉ์ Result Conversion ํญ๋ชฉ์ ์ฐธ์กฐํ์ธ์.
ํด๋น ๋ฉ์๋๋ static์ด๊ฑฐ๋ ์ธ์คํด์ค ๋ฉ์๋์ผ ์ ์์ผ๋ฉฐ, ์ ๊ทผ ์ ํ์๋ public, protected, ํจํค์ง ํ๋ผ์ด๋น(package-private), private ๋ชจ๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ฉ์๋๋ฅผ ํฌํจํ๋ ํด๋์ค๋ ์ต์์ ํด๋์ค์ด๊ฑฐ๋ ์ค์ฒฉ ํด๋์ค(nested class) ์ผ ์ ์์ผ๋ฉฐ, ์ธ์คํด์ค๋ฅผ ์์ฑํ๋ ค๋ ์์น์์ ์ ๊ทผ ๊ฐ๋ฅํ๊ธฐ๋ง ํ๋ฉด ํด๋์ค์ ๊ฐ์์ฑ๋ ์ ํ๋์ง ์์ต๋๋ค.
์ฐธ๊ณ
@Tool ์ด๋ ธํ ์ด์ ์ด ๋ถ์ ๋ฉ์๋๋ฅผ AOT ์ปดํ์ผํ๋ ค๋ฉด, ํด๋น ๋ฉ์๋๊ฐ ํฌํจ๋ ํด๋์ค๊ฐ Spring Bean(์: @Component)์ด์ด์ผ ํฉ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด GraalVM ์ปดํ์ผ๋ฌ์ ํ์ํ ๊ตฌ์ฑ์ ์ง์ ์ ๊ณตํด์ผ ํ๋ฉฐ, ์๋ฅผ ๋ค์ด ํด๋์ค์ @RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_METHODS) ์ด๋ ธํ ์ด์ ์ ๋ถ์ด๋ ๋ฐฉ์์ด ์์ต๋๋ค.
๋ฉ์๋๋ ์ธ์๋ฅผ 0๊ฐ ๋๋ ์ฌ๋ฌ ๊ฐ ๊ฐ์ง ์ ์์ผ๋ฉฐ, ๋๋ถ๋ถ์ ํ์ (๊ธฐ๋ณธํ, POJO, enum, ๋ฆฌ์คํธ, ๋ฐฐ์ด, ๋งต ๋ฑ)์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ง์ฐฌ๊ฐ์ง๋ก, ๋ฉ์๋์ ๋ฐํ ํ์ ๋ ๋๋ถ๋ถ์ ํ์ ์ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ void๋ ํ์ฉ๋ฉ๋๋ค. ๋จ, ๋ฐํ๊ฐ์ด ์๋ ๊ฒฝ์ฐ์๋ ์ง๋ ฌํ ๊ฐ๋ฅํ ํ์ ์ด์ด์ผ ํ๋ฉฐ, ์ด ๊ฐ์ ์ง๋ ฌํ๋์ด ๋ชจ๋ธ์ ์ ๋ฌ๋ฉ๋๋ค.
์ฐธ๊ณ
์ผ๋ถ ํ์ ์ ์ง์๋์ง ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์ Method Tool Limitations๋ฅผ ์ฐธ๊ณ ํ์ธ์.
Spring AI๋ @Tool์ด ๋ถ์ ๋ฉ์๋์ ์ ๋ ฅ ํ๋ผ๋ฏธํฐ์ ๋ํด ์๋์ผ๋ก JSON ์คํค๋ง๋ฅผ ์์ฑํฉ๋๋ค. ์ด ์คํค๋ง๋ ๋ชจ๋ธ์ด ๋๊ตฌ๋ฅผ ์ด๋ป๊ฒ ํธ์ถํ ์ง ์ดํดํ๊ณ ๋๊ตฌ ์์ฒญ์ ์ค๋นํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ์ ๋ ฅ ํ๋ผ๋ฏธํฐ์ ๋ํด ๋ ๋ง์ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ ค๋ฉด @ToolParam ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ์ค๋ช ๋๋ ํ์ ์ฌ๋ถ ๋ฑ์ ์ง์ ํ ์ ์์ต๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ ์ ๋ ฅ ํ๋ผ๋ฏธํฐ๋ ํ์(required)๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
class DateTimeTools {
@Tool(description = "Set a user alarm for the given time")
void setAlarm(@ToolParam(description = "Time in ISO-8601 format") String time) {
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
System.out.println("Alarm set for " + alarmTime);
}
}
@ToolParam ์ด๋ ธํ ์ด์ ์ ๋๊ตฌ์ ํ๋ผ๋ฏธํฐ์ ๋ํด ๋ค์๊ณผ ๊ฐ์ ํต์ฌ ์ ๋ณด๋ฅผ ์ ๊ณตํ ์ ์์ต๋๋ค:
- description: ํด๋น ํ๋ผ๋ฏธํฐ์ ๋ํ ์ค๋ช ์ ๋๋ค. ๋ชจ๋ธ์ด ์ด ํ๋ผ๋ฏธํฐ๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํ ์ง, ์๋ฅผ ๋ค์ด ์ด๋ค ํ์์ธ์ง, ํ์ฉ๋๋ ๊ฐ์ ๋ฌด์์ธ์ง ๋ฑ์ ์ดํดํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
- required: ์ด ํ๋ผ๋ฏธํฐ๊ฐ ํ์์ธ์ง ์ ํ์ธ์ง๋ฅผ ์ง์ ํฉ๋๋ค. ๊ธฐ๋ณธ๊ฐ์ true์ด๋ฉฐ, ์ฆ ๋ชจ๋ ํ๋ผ๋ฏธํฐ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํ์๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
ํ๋ผ๋ฏธํฐ์ @Nullable ์ด๋ ธํ ์ด์ ์ด ์ง์ ๋์ด ์๋ค๋ฉด, @ToolParam์์ ๋ช ์์ ์ผ๋ก required = true๋ก ์ง์ ํ์ง ์์ ํ, ์ ํ์ (optional)์ผ๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค.
๋ํ @ToolParam ์ธ์๋ Swagger์ @Schema ๋๋ Jackson์ @JsonProperty ์ด๋ ธํ ์ด์ ๋ ํจ๊ป ์ฌ์ฉํ ์ ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์ JSON Schema ํญ๋ชฉ์ ์ฐธ์กฐํ์ธ์.
ChatClient์ ๋๊ตฌ ์ถ๊ฐํ๊ธฐ
MethodToolCallback ์ธ์คํด์ค๋ฅผ ChatClient์ tools() ๋ฉ์๋์ ์ ๋ฌํ๋ฉด ๋ฉ๋๋ค. ์ด ๋๊ตฌ๋ ํด๋น ์ฑํ ์์ฒญ์๋ง ์ ํจํฉ๋๋ค.
ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.tools(toolCallback)
.call()
.content();
๋ด๋ถ์ ์ผ๋ก ChatClient๋ ๋๊ตฌ ํด๋์ค ์ธ์คํด์ค์ ์๋ @Tool ์ด๋ ธํ ์ด์ ์ด ๋ถ์ ๊ฐ ๋ฉ์๋๋ก๋ถํฐ ์๋์ผ๋ก ToolCallback์ ์์ฑํ๊ณ , ์ด๋ฅผ ๋ชจ๋ธ์ ์ ๋ฌํฉ๋๋ค. ์ง์ ToolCallback์ ์์ฑํ๊ณ ์ถ๋ค๋ฉด, ToolCallbacks ์ ํธ๋ฆฌํฐ ํด๋์ค๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatClient์ ๊ธฐ๋ณธ ๋๊ตฌ(Default Tools) ์ถ๊ฐํ๊ธฐ
์ ์ธ์ ๋ฐฉ์(declarative specification approach)์ ์ฌ์ฉํ ๋๋, ๋๊ตฌ ํด๋์ค ์ธ์คํด์ค๋ฅผ defaultTools() ๋ฉ์๋์ ์ ๋ฌํ์ฌ ChatClient.Builder์ ๊ธฐ๋ณธ ๋๊ตฌ๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค. ๊ธฐ๋ณธ ๋๊ตฌ์ ๋ฐํ์ ๋๊ตฌ๊ฐ ๋ชจ๋ ์ ๊ณต๋๋ ๊ฒฝ์ฐ, ๋ฐํ์ ๋๊ตฌ๊ฐ ๊ธฐ๋ณธ ๋๊ตฌ๋ฅผ ์์ ํ ๋ฎ์ด์๋๋ค.
์ฃผ์
๊ธฐ๋ณธ ๋๊ตฌ๋ ๋์ผํ ChatClient.Builder๋ก ์์ฑ๋ ๋ชจ๋ ChatClient ์ธ์คํด์ค์ ๋ชจ๋ ์์ฒญ์ ๊ณต์ ๋ฉ๋๋ค. ์ฌ๋ฌ ์์ฒญ์์ ๊ณตํต์ผ๋ก ์ฌ์ฉํ๋ ๋๊ตฌ์ ์ ์ฉํ์ง๋ง, ์๋์น ์๊ฒ ๋๊ตฌ๊ฐ ๋ ธ์ถ๋ ์ ์์ผ๋ฏ๋ก ์ฃผ์๊ฐ ํ์ํฉ๋๋ค.
ChatModel chatModel = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(new DateTimeTools())
.build();
ChatModel์ ๋๊ตฌ ์ถ๊ฐํ๊ธฐ
์ ์ธ์ ๋ฐฉ์(declarative specification approach)์ ์ฌ์ฉํ ๋๋, ๋๊ตฌ ํด๋์ค ์ธ์คํด์ค๋ฅผ ToolCallingChatOptions์ toolCallbacks() ๋ฉ์๋์ ์ ๋ฌํ์ฌ ChatModel ํธ์ถ ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ์ถ๊ฐ๋ ๋๊ตฌ๋ ํด๋น ์ฑํ ์์ฒญ์๋ง ์ ํจํฉ๋๋ค.
ChatModel chatModel = ...
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(dateTimeTools)
.build();
Prompt prompt = new Prompt("What day is tomorrow?", chatOptions);
chatModel.call(prompt);
ChatModel์ ๊ธฐ๋ณธ ๋๊ตฌ(Default Tools) ์ถ๊ฐํ๊ธฐ
์ ์ธ์ ๋ฐฉ์(declarative specification approach)์ ์ฌ์ฉํ ๋๋, ChatModel์ ์์ฑํ ๋ ์ฌ์ฉํ๋ ToolCallingChatOptions์ toolCallbacks() ๋ฉ์๋์ ๋๊ตฌ ํด๋์ค ์ธ์คํด์ค๋ฅผ ์ ๋ฌํ์ฌ ๊ธฐ๋ณธ ๋๊ตฌ๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค.
๊ธฐ๋ณธ ๋๊ตฌ์ ๋ฐํ์ ๋๊ตฌ๊ฐ ๋ชจ๋ ์ ๊ณต๋๋ ๊ฒฝ์ฐ, ๋ฐํ์ ๋๊ตฌ๊ฐ ๊ธฐ๋ณธ ๋๊ตฌ๋ฅผ ์์ ํ ๋ฎ์ด์๋๋ค.
์ฃผ์
๊ธฐ๋ณธ ๋๊ตฌ๋ ํด๋น ChatModel ์ธ์คํด์ค์์ ์ํ๋๋ ๋ชจ๋ ์ฑํ ์์ฒญ์ ๊ณต์ ๋ฉ๋๋ค.
์ฌ๋ฌ ์์ฒญ์์ ๋ฐ๋ณต์ ์ผ๋ก ์ฌ์ฉํ๋ ๋๊ตฌ์๋ ์ ์ฉํ์ง๋ง, ์์น ์๋ ์์ฒญ์ ๋ ธ์ถ๋ ์ ์์ผ๋ฏ๋ก ์ ์คํ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolCallbacks(dateTimeTools)
.build())
.build();
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์ ์ง์ : MethodToolCallback
๋ฉ์๋๋ฅผ ๋๊ตฌ๋ก ์ฌ์ฉํ๋ ค๋ฉด, MethodToolCallback์ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ์์ฑํ์ฌ ๋๊ตฌ๋ก ๋ง๋ค ์ ์์ต๋๋ค.
class DateTimeTools {
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
MethodToolCallback.Builder๋ MethodToolCallback ์ธ์คํด์ค๋ฅผ ์์ฑํ ์ ์๋๋ก ํด์ฃผ๋ฉฐ, ๋๊ตฌ์ ๋ํ ์ฃผ์ ์ ๋ณด๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค:
- toolDefinition: ๋๊ตฌ์ ์ด๋ฆ, ์ค๋ช , ์ ๋ ฅ ์คํค๋ง๋ฅผ ์ ์ํ๋ ToolDefinition ์ธ์คํด์ค์ ๋๋ค. ToolDefinition.Builder ํด๋์ค๋ฅผ ์ฌ์ฉํด ์์ฑํ ์ ์์ผ๋ฉฐ, ํ์ ํญ๋ชฉ์ ๋๋ค.
- toolMetadata: ๊ฒฐ๊ณผ๋ฅผ ํด๋ผ์ด์ธํธ์ ์ง์ ๋ฐํํ ์ง ์ฌ๋ถ, ๊ฒฐ๊ณผ ๋ณํ ๋ฐฉ์ ๋ฑ์ ์ ์ํ๋ ToolMetadata ์ธ์คํด์ค์ ๋๋ค. ToolMetadata.Builder ํด๋์ค๋ก ์์ฑํ ์ ์์ต๋๋ค.
- toolMethod: ๋๊ตฌ๋ก ์ฌ์ฉํ ๋ฉ์๋๋ฅผ ๋ํ๋ด๋ java.lang.reflect.Method ์ธ์คํด์ค์ ๋๋ค. ํ์ ํญ๋ชฉ์ ๋๋ค.
- toolObject: ๋๊ตฌ ๋ฉ์๋๋ฅผ ํฌํจํ๊ณ ์๋ ๊ฐ์ฒด ์ธ์คํด์ค์ ๋๋ค. ๋ฉ์๋๊ฐ static์ผ ๊ฒฝ์ฐ์๋ ์ด ํ๋ผ๋ฏธํฐ๋ฅผ ์๋ตํ ์ ์์ต๋๋ค.
- toolCallResultConverter: ๋๊ตฌ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๋ฌธ์์ด๋ก ๋ณํํด AI ๋ชจ๋ธ์ ์ ๋ฌํ๋ ๋ฐ ์ฌ์ฉํ ToolCallResultConverter ์ธ์คํด์ค์ ๋๋ค. ์ง์ ํ์ง ์์ผ๋ฉด ๊ธฐ๋ณธ ๋ณํ๊ธฐ์ธ DefaultToolCallResultConverter๊ฐ ์ฌ์ฉ๋ฉ๋๋ค.
ToolDefinition.Builder๋ฅผ ์ฌ์ฉํ๋ฉด ๋๊ตฌ ์ด๋ฆ, ์ค๋ช , ์ ๋ ฅ ์คํค๋ง๋ฅผ ์ ์ํ ์ ์์ต๋๋ค:
- name: ๋๊ตฌ์ ์ด๋ฆ. ์๋ต ์ ๋ฉ์๋ ์ด๋ฆ์ด ์ฌ์ฉ๋จ. ํ๋์ ์ฑํ ์์ฒญ ๋ด์์ ์ด๋ฆ์ ๊ณ ์ ํด์ผ ํฉ๋๋ค.
- description: ๋๊ตฌ ์ค๋ช . ์๋ต ์ ๋ฉ์๋ ์ด๋ฆ์ด ์ฌ์ฉ๋์ง๋ง, ๋ชจ๋ธ์ ์ ํํ ์ดํด๋ฅผ ์ํด ์์ธํ ์ค๋ช ์ ๊ณต์ด ๊ถ์ฅ๋ฉ๋๋ค.
- inputSchema: ๋๊ตฌ์ ์ ๋ ฅ ํ๋ผ๋ฏธํฐ์ ๋ํ JSON ์คํค๋ง. ์๋ต ์ ๋ฉ์๋ ์๊ทธ๋์ฒ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์๋ ์์ฑ๋ฉ๋๋ค. @ToolParam ์ด๋ ธํ ์ด์ ์ ํตํด ํ๋ผ๋ฏธํฐ์ ๋ํ ์ถ๊ฐ ์ ๋ณด ์ ๊ณต์ด ๊ฐ๋ฅํฉ๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ ํ๋ผ๋ฏธํฐ๋ ํ์๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
ToolMetadata.Builder๋ฅผ ์ฌ์ฉํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋๊ตฌ ์ค์ ์ ์ง์ ํ ์ ์์ต๋๋ค:
- returnDirect: ๊ฒฐ๊ณผ๋ฅผ ํด๋ผ์ด์ธํธ์ ์ง์ ๋ฐํํ ์ง, ๋ชจ๋ธ์ ์ ๋ฌํ ์ง ์ฌ๋ถ๋ฅผ ์๋ฏธํฉ๋๋ค. ์์ธํ ๋ด์ฉ์ Return Direct๋ฅผ ์ฐธ๊ณ ํ์ธ์.
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolCallback toolCallback = MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(method)
.description("Get the current date and time in the user's timezone")
.build())
.toolMethod(method)
.toolObject(new DateTimeTools())
.build();
๋ฉ์๋๋ static ๋๋ ์ธ์คํด์ค ๋ชจ๋ ๊ฐ๋ฅํ๋ฉฐ, public, protected, package-private, private ๋ฑ ๊ฐ์์ฑ์ ์ ํ์ด ์์ต๋๋ค.
ํด๋์ค๋ ์ต์์ ๋๋ ์ค์ฒฉ ํด๋์ค ๋ชจ๋ ๊ฐ๋ฅํ๋ฉฐ, ์ธ์คํด์ค ์์ฑ ๊ฐ๋ฅํ ๋ฒ์ ๋ด์์ ์ ๊ทผ ๊ฐ๋ฅํด์ผ ํฉ๋๋ค.
์ฐธ๊ณ
Spring AI๋ ๋๊ตฌ ๋ฉ์๋๋ฅผ ํฌํจํ ํด๋์ค๊ฐ Spring ๋น(@Component ๋ฑ) ์ผ ๊ฒฝ์ฐ, ํด๋น ๋ฉ์๋์ ๋ํด AOT ์ปดํ์ผ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ง์ํฉ๋๋ค. ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ์๋ GraalVM ์ปดํ์ผ๋ฌ์ ํ์ํ ์ค์ ์ ์ง์ ์ ๊ณตํด์ผ ํฉ๋๋ค.
์๋ฅผ ๋ค์ด, ํด๋์ค์ @RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_METHODS) ์ด๋ ธํ ์ด์ ์ ์ถ๊ฐํด์ผ ํฉ๋๋ค.
๋ฉ์๋๋ ์ธ์๋ฅผ 0๊ฐ ์ด์ ์ ์ํ์ค ์ ์์ผ๋ฉฐ, ๋๋ถ๋ถ์ ํ์ (๊ธฐ๋ณธํ, POJO, enum, ๋ฆฌ์คํธ, ๋ฐฐ์ด, ๋งต ๋ฑ)์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ง์ฐฌ๊ฐ์ง๋ก ๋ฐํ ํ์ ๋ ๋๋ถ๋ถ์ ํ์ ์ ์ฌ์ฉํ ์ ์๊ณ , void ๋ฐํ๋ ๊ฐ๋ฅํฉ๋๋ค. ๋จ, ๋ฐํ๊ฐ์ด ์์ ๊ฒฝ์ฐ ํด๋น ํ์ ์ ์ง๋ ฌํ๊ฐ ๊ฐ๋ฅํด์ผ ํ๋ฉฐ, ๊ฒฐ๊ณผ๋ ์ง๋ ฌํ๋์ด ๋ชจ๋ธ๋ก ์ ๋ฌ๋ฉ๋๋ค.
์ฐธ๊ณ
์ผ๋ถ ํ์ ์ ์ง์๋์ง ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์ Method Tool Limitations ํญ๋ชฉ์ ์ฐธ๊ณ ํด ์ฃผ์๊ธฐ ๋ฐ๋๋๋ค.
๋ฉ์๋๊ฐ static์ธ ๊ฒฝ์ฐ์๋ toolObject() ๋ฉ์๋๋ฅผ ์๋ตํ์ ๋ ๋ฉ๋๋ค. ํด๋น ๊ฐ์ฒด ์ธ์คํด์ค๊ฐ ํ์ํ์ง ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
class DateTimeTools {
static String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolCallback toolCallback = MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(method)
.description("Get the current date and time in the user's timezone")
.build())
.toolMethod(method)
.build();
Spring AI๋ ๋ฉ์๋์ ์ ๋ ฅ ํ๋ผ๋ฏธํฐ์ ๋ํ JSON ์คํค๋ง๋ฅผ ์๋์ผ๋ก ์์ฑํฉ๋๋ค. ์ด ์คํค๋ง๋ ๋ชจ๋ธ์ด ๋๊ตฌ๋ฅผ ์ด๋ป๊ฒ ํธ์ถํ ์ง ์ดํดํ๊ณ ๋๊ตฌ ์์ฒญ์ ์ค๋นํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. @ToolParam ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ฉด ํ๋ผ๋ฏธํฐ์ ๋ํ ์ค๋ช ์ด๋ ํ์ ์ฌ๋ถ ๋ฑ์ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์ ๊ณตํ ์ ์์ต๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ ์ ๋ ฅ ํ๋ผ๋ฏธํฐ๋ ํ์(required)๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.ai.tool.annotation.ToolParam;
class DateTimeTools {
void setAlarm(@ToolParam(description = "Time in ISO-8601 format") String time) {
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
System.out.println("Alarm set for " + alarmTime);
}
}
@ToolParam ์ด๋ ธํ ์ด์ ์ ๋๊ตฌ ํ๋ผ๋ฏธํฐ์ ๋ํ ํต์ฌ ์ ๋ณด๋ฅผ ์ ๊ณตํ ์ ์๋๋ก ํด์ค๋๋ค:
- description: ํ๋ผ๋ฏธํฐ์ ๋ํ ์ค๋ช ์ ๋๋ค. ๋ชจ๋ธ์ด ํด๋น ํ๋ผ๋ฏธํฐ๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํ ์ง ๋ ์ ์ดํดํ ์ ์๋๋ก ๋์์ค๋๋ค. ์๋ฅผ ๋ค์ด, ํ๋ผ๋ฏธํฐ์ ํ์์ด๋ ํ์ฉ๋๋ ๊ฐ ๋ฑ์ด ์ฌ๊ธฐ์ ํฌํจ๋ ์ ์์ต๋๋ค.
- required: ํด๋น ํ๋ผ๋ฏธํฐ๊ฐ ํ์์ธ์ง ์ ํ์ธ์ง ์ฌ๋ถ๋ฅผ ๋ํ๋ ๋๋ค. ๊ธฐ๋ณธ๊ฐ์ ํ์์ ๋๋ค.
ํ๋ผ๋ฏธํฐ์ @Nullable ์ด๋ ธํ ์ด์ ์ด ํจ๊ป ์ง์ ๋์ด ์๋ค๋ฉด, @ToolParam์์ ๋ช ์์ ์ผ๋ก required = true๋ก ์ค์ ํ์ง ์๋ ํ ์ ํ์ ์ผ๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค.
@ToolParam ์ธ์๋ Swagger์ @Schema๋ Jackson์ @JsonProperty ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์ JSON ์คํค๋ง ๊ด๋ จ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.
ChatClient ๋ฐ ChatModel์ ๋๊ตฌ ์ถ๊ฐํ๊ธฐ
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์ ์ง์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ๋๋ MethodToolCallback ์ธ์คํด์ค๋ฅผ ChatClient์ tools() ๋ฉ์๋์ ์ ๋ฌํ ์ ์์ต๋๋ค. ์ด ๋๊ตฌ๋ ์ถ๊ฐ๋ ํด๋น ์ฑํ ์์ฒญ์๋ง ์ฌ์ฉ ๊ฐ๋ฅํฉ๋๋ค.
ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("What day is tomorrow?")
.tools(toolCallback)
.call()
.content();
ChatClient์ ๊ธฐ๋ณธ ๋๊ตฌ ์ถ๊ฐํ๊ธฐ
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์ ์ง์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ๋๋ MethodToolCallback ์ธ์คํด์ค๋ฅผ defaultTools() ๋ฉ์๋์ ์ ๋ฌํ์ฌ ChatClient.Builder์ ๊ธฐ๋ณธ ๋๊ตฌ๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค. ๊ธฐ๋ณธ ๋๊ตฌ์ ๋ฐํ์ ๋๊ตฌ๊ฐ ๋ชจ๋ ์ ๊ณต๋๋ ๊ฒฝ์ฐ, ๋ฐํ์ ๋๊ตฌ๊ฐ ๊ธฐ๋ณธ ๋๊ตฌ๋ฅผ ์์ ํ ๋ฎ์ด์๋๋ค.
์ฃผ์
๊ธฐ๋ณธ ๋๊ตฌ๋ ๋์ผํ ChatClient.Builder๋ก ์์ฑ๋ ๋ชจ๋ ChatClient ์ธ์คํด์ค์ ๋ชจ๋ ์ฑํ ์์ฒญ์ ๊ณต์ ๋ฉ๋๋ค. ์ฌ๋ฌ ์ฑํ ์์ฒญ์์ ๊ณตํต์ผ๋ก ์ฌ์ฉํ๋ ๋๊ตฌ์๋ ์ ์ฉํ์ง๋ง, ์ฃผ์ํ์ง ์์ผ๋ฉด ์ํ์ง ์๋ ์์ฒญ์๋ ๋๊ตฌ๊ฐ ๋ ธ์ถ๋ ์ ์๋ ์ํ์ด ์์ต๋๋ค.
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(toolCallback)
.build();
ChatModel์ ๋๊ตฌ ์ถ๊ฐํ๊ธฐ
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์ ์ง์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ๋๋ MethodToolCallback ์ธ์คํด์ค๋ฅผ ToolCallingChatOptions์ toolCallbacks() ๋ฉ์๋์ ์ ๋ฌํ์ฌ ChatModel ํธ์ถ ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ๋๊ตฌ๋ ์ถ๊ฐ๋ ํด๋น ์ฑํ ์์ฒญ์๋ง ์ ํจํฉ๋๋ค.
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build();
Prompt prompt = new Prompt("What day is tomorrow?", chatOptions);
chatModel.call(prompt);
ChatModel์ ๊ธฐ๋ณธ ๋๊ตฌ ์ถ๊ฐํ๊ธฐ
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์ ์ง์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ๋๋ ChatModel ์์ฑ ์ ์ฌ์ฉํ๋ ToolCallingChatOptions์ toolCallbacks() ๋ฉ์๋์ MethodToolCallback ์ธ์คํด์ค๋ฅผ ์ ๋ฌํ์ฌ ๊ธฐ๋ณธ ๋๊ตฌ๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค. ๊ธฐ๋ณธ ๋๊ตฌ์ ๋ฐํ์ ๋๊ตฌ๊ฐ ๋ชจ๋ ์ ๊ณต๋๋ ๊ฒฝ์ฐ, ๋ฐํ์ ๋๊ตฌ๊ฐ ๊ธฐ๋ณธ ๋๊ตฌ๋ฅผ ์์ ํ ๋ฎ์ด์๋๋ค.
์ฃผ์
๊ธฐ๋ณธ ๋๊ตฌ๋ ํด๋น ChatModel ์ธ์คํด์ค์์ ์ํ๋๋ ๋ชจ๋ ์ฑํ ์์ฒญ์ ๊ณต์ ๋ฉ๋๋ค. ์ฌ๋ฌ ์์ฒญ์์ ๊ณตํต์ผ๋ก ์ฌ์ฉํ๋ ๋๊ตฌ์๋ ์ ์ฉํ์ง๋ง, ์ฃผ์ํ์ง ์์ผ๋ฉด ์ํ์ง ์๋ ์์ฒญ์๋ ๋๊ตฌ๊ฐ ๋ ธ์ถ๋ ์ ์๋ ์ํ์ด ์์ต๋๋ค.
ToolCallback toolCallback = ...
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build())
.build();
๋ฉ์๋ ๋๊ตฌ์ ์ ํ ์ฌํญ
๋ฉ์๋๋ฅผ ๋๊ตฌ๋ก ์ฌ์ฉํ ๋, ๋ค์๊ณผ ๊ฐ์ ํ์ ๋ค์ ํ์ฌ ํ๋ผ๋ฏธํฐ๋ ๋ฐํ ํ์ ์ผ๋ก ์ง์๋์ง ์์ต๋๋ค:
- Optional
- ๋น๋๊ธฐ ํ์ (์: CompletableFuture, Future)
- ๋ฆฌ์กํฐ๋ธ ํ์ (์: Flow, Mono, Flux)
- ํจ์ํ ํ์ (์: Function, Supplier, Consumer)
ํจ์ํ ํ์ ์ ํจ์ ๊ธฐ๋ฐ ๋๊ตฌ ์ง์ ๋ฐฉ์(function-based tool specification approach)์ ํตํด ์ง์๋ฉ๋๋ค. ์์ธํ ๋ด์ฉ์ Functions as Tools ํญ๋ชฉ์ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.
ํจ์๋ก์์ ๋๊ตฌ
Spring AI๋ ํจ์๋ก ๋๊ตฌ๋ฅผ ์ง์ ํ๋ ๊ธฐ๋ฅ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ง์ํ๋ฉฐ, ์ด๋ ์ ์์ค FunctionToolCallback ๊ตฌํ์ ์ฌ์ฉํ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ด๋ ๋ฐํ์์ @Bean์ผ๋ก ์ ์๋ ๋๊ตฌ๋ฅผ ๋์ ์ผ๋ก ํด์ํ๋ ๋ฐฉ์์ผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์ ์ง์ : FunctionToolCallback
Function, Supplier, Consumer ๋๋ BiFunction๊ณผ ๊ฐ์ ํจ์ํ ํ์ ์ FunctionToolCallback์ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ๊ตฌ์ฑํ์ฌ ๋๊ตฌ๋ก ๋ณํํ ์ ์์ต๋๋ค.
public class WeatherService implements Function<WeatherRequest, WeatherResponse> {
public WeatherResponse apply(WeatherRequest request) {
return new WeatherResponse(30.0, Unit.C);
}
}
public enum Unit { C, F }
public record WeatherRequest(String location, Unit unit) {}
public record WeatherResponse(double temp, Unit unit) {}
FunctionToolCallback.Builder๋ FunctionToolCallback ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ณ ๋๊ตฌ์ ๋ํ ์ฃผ์ ์ ๋ณด๋ฅผ ์ค์ ํ ์ ์๋๋ก ํฉ๋๋ค.
- name: ๋๊ตฌ์ ์ด๋ฆ. AI ๋ชจ๋ธ์ ๋๊ตฌ๋ฅผ ํธ์ถํ ๋ ์ด ์ด๋ฆ์ ์ฌ์ฉํ์ฌ ๋๊ตฌ๋ฅผ ์๋ณํฉ๋๋ค. ๋ฐ๋ผ์ ๋์ผํ ์ปจํ ์คํธ ๋ด์์ ๊ฐ์ ์ด๋ฆ์ ๊ฐ์ง ๋๊ตฌ๊ฐ ๋ ๊ฐ ์ด์ ์กด์ฌํด์๋ ์ ๋ฉ๋๋ค. ๋ชจ๋ธ์ด ์ฒ๋ฆฌํ๋ ํน์ ์ฑํ ์์ฒญ ๋ด์์ ์ด ์ด๋ฆ์ ๊ณ ์ ํด์ผ ํ๋ฉฐ, ํ์ ํญ๋ชฉ์ ๋๋ค.
- toolFunction: ๋๊ตฌ ๋ฉ์๋๋ฅผ ๋ํ๋ด๋ ํจ์ํ ๊ฐ์ฒด(Function, Supplier, Consumer, ๋๋ BiFunction). ํ์ ํญ๋ชฉ์ ๋๋ค.
- description: ๋๊ตฌ์ ๋ํ ์ค๋ช . ๋ชจ๋ธ์ด ๋๊ตฌ๋ฅผ ์ธ์ ์ด๋ป๊ฒ ํธ์ถํ ์ง ์ดํดํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ์ด ์ค๋ช ์ ์ ๊ณตํ์ง ์์ผ๋ฉด ๋ฉ์๋ ์ด๋ฆ์ด ์ค๋ช ์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค. ํ์ง๋ง ๋๊ตฌ์ ๋ชฉ์ ๊ณผ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ๋ชจ๋ธ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ดํดํ ์ ์๋๋ก ์์ธํ ์ค๋ช ์ ์ ๊ณตํ๋ ๊ฒ์ด ๊ฐ๋ ฅํ ๊ถ์ฅ๋ฉ๋๋ค. ์ค๋ช ์ด ๋ถ์คํ ๊ฒฝ์ฐ, ๋ชจ๋ธ์ด ๋๊ตฌ๋ฅผ ์ฌ์ฉํด์ผ ํ ์ํฉ์์๋ ์ฌ์ฉํ์ง ์๊ฑฐ๋ ์๋ชป ์ฌ์ฉํ ์ ์์ต๋๋ค.
- inputType: ํจ์ ์ ๋ ฅ๊ฐ์ ํ์ . ํ์ ํญ๋ชฉ์ ๋๋ค.
- inputSchema: ๋๊ตฌ์ ์ ๋ ฅ ๋งค๊ฐ๋ณ์์ ๋ํ JSON ์คํค๋ง. ์ ๊ณตํ์ง ์์ผ๋ฉด inputType์ ๊ธฐ๋ฐ์ผ๋ก ์๋ ์์ฑ๋ฉ๋๋ค. @ToolParam ์ ๋ ธํ ์ด์ ์ ์ฌ์ฉํ์ฌ ๋งค๊ฐ๋ณ์์ ๋ํ ์ค๋ช ์ด๋ ํ์ ์ฌ๋ถ ๋ฑ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์ง์ ํ ์ ์์ต๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ ๋งค๊ฐ๋ณ์๋ ํ์๋ก ๊ฐ์ฃผ๋ฉ๋๋ค. ์์ธํ ๋ด์ฉ์ JSON Schema ์น์ ์ ์ฐธ์กฐํ๋ฉด ๋ฉ๋๋ค.
- toolMetadata: ๊ฒฐ๊ณผ๋ฅผ ํด๋ผ์ด์ธํธ์ ์ง์ ๋ฐํํ ์ง ์ฌ๋ถ, ๊ฒฐ๊ณผ ๋ณํ๊ธฐ ๋ฑ์ ์ถ๊ฐ ์ค์ ์ ์ ์ํ๋ ToolMetadata ์ธ์คํด์ค์ ๋๋ค. ToolMetadata.Builder ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ ์์ฑํ ์ ์์ต๋๋ค.
- toolCallResultConverter: ๋๊ตฌ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๋ฌธ์์ด๋ก ๋ณํํ์ฌ AI ๋ชจ๋ธ์ ๋ฐํํ ๋ ์ฌ์ฉํ ToolCallResultConverter ์ธ์คํด์ค. ์ ๊ณตํ์ง ์์ผ๋ฉด ๊ธฐ๋ณธ ๋ณํ๊ธฐ(DefaultToolCallResultConverter)๊ฐ ์ฌ์ฉ๋ฉ๋๋ค.
ToolMetadata.Builder๋ ToolMetadata ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ณ ๋๊ตฌ์ ๋ํ ์ถ๊ฐ ์ค์ ์ ์ ์ํ ์ ์๊ฒ ํฉ๋๋ค.
- returnDirect: ๋๊ตฌ ์คํ ๊ฒฐ๊ณผ๋ฅผ ๋ชจ๋ธ์ด ์๋๋ผ ํด๋ผ์ด์ธํธ(ํธ์ถ์)์๊ฒ ์ง์ ๋ฐํํ ์ง ์ฌ๋ถ๋ฅผ ์ค์ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ๊ฒ์ ๋๊ตฌ์์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ๋ก ์ฌ์ฉ์์๊ฒ ๋ฐํํ๊ณ ์ถ์ ๊ฒฝ์ฐ returnDirect(true)๋ก ์ค์ ํ ์ ์์ต๋๋ค.
ToolCallback toolCallback = FunctionToolCallback
.builder("currentWeather", new WeatherService())
.description("Get the weather in location")
.inputType(WeatherRequest.class)
.build();
ํจ์์ ์ ๋ ฅ๊ณผ ์ถ๋ ฅ์ Void ๋๋ POJO์ผ ์ ์์ต๋๋ค. ์ ๋ ฅ ๋ฐ ์ถ๋ ฅ์ผ๋ก ์ฌ์ฉ๋๋ POJO๋ ์ง๋ ฌํ ๊ฐ๋ฅํด์ผ ํ๋ฉฐ, ์ด๋ ๊ฒฐ๊ณผ๊ฐ ์ง๋ ฌํ๋์ด ๋ชจ๋ธ๋ก ๋ฐํ๋๊ธฐ ๋๋ฌธ์ ๋๋ค. ํจ์ ์์ฒด์ ์ ๋ ฅ ๋ฐ ์ถ๋ ฅ ํ์ ๋ชจ๋ public์ด์ด์ผ ํฉ๋๋ค.
์ฐธ๊ณ
์ผ๋ถ ํ์ ์ ์ง์๋์ง ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์ Function Tool Limitations ํญ๋ชฉ์ ์ฐธ์กฐํ์ธ์.
ChatClient์ ๋๊ตฌ ์ถ๊ฐํ๊ธฐ
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์ ์ง์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ๊ฒฝ์ฐ, FunctionToolCallback ์ธ์คํด์ค๋ฅผ ChatClient์ tools() ๋ฉ์๋์ ์ ๋ฌํ์ค ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ์ถ๊ฐ๋ ๋๊ตฌ๋ ํด๋น ์ฑํ ์์ฒญ์๋ง ์ฌ์ฉ ๊ฐ๋ฅํฉ๋๋ค.
ToolCallback toolCallback = ...
ChatClient.create(chatModel)
.prompt("What's the weather like in Copenhagen?")
.tools(toolCallback)
.call()
.content();
ChatClient์ ๊ธฐ๋ณธ ๋๊ตฌ ์ถ๊ฐํ๊ธฐ
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์ ์ง์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ๊ฒฝ์ฐ, FunctionToolCallback ์ธ์คํด์ค๋ฅผ ChatClient.Builder์ defaultTools() ๋ฉ์๋์ ์ ๋ฌํ์ฌ ๊ธฐ๋ณธ ๋๊ตฌ๋ก ์ถ๊ฐํ์ค ์ ์์ต๋๋ค. ๊ธฐ๋ณธ ๋๊ตฌ์ ๋ฐํ์ ๋๊ตฌ๊ฐ ๋ชจ๋ ์ ๊ณต๋๋ ๊ฒฝ์ฐ, ๋ฐํ์ ๋๊ตฌ๊ฐ ๊ธฐ๋ณธ ๋๊ตฌ๋ฅผ ์์ ํ ๋ฎ์ด์๋๋ค.
์ฃผ์
๋ณธ ๋๊ตฌ๋ ๋์ผํ ChatClient.Builder๋ก ์์ฑ๋ ๋ชจ๋ ChatClient ์ธ์คํด์ค์์ ์ํ๋๋ ๋ชจ๋ ์ฑํ ์์ฒญ์ ๊ณต์ ๋ฉ๋๋ค. ์ด๋ฌํ ๋๊ตฌ๋ ์ฌ๋ฌ ์ฑํ ์์ฒญ์์ ๊ณตํต์ ์ผ๋ก ์ฌ์ฉ๋๋ ๋๊ตฌ๋ฅผ ์ ์ํ ๋ ์ ์ฉํ์ง๋ง, ์ฃผ์ํด์ ์ฌ์ฉํ์ง ์์ผ๋ฉด ์์น ์๋ ์ํฉ์์ ๋๊ตฌ๊ฐ ๋ ธ์ถ๋ ์ํ์ด ์์ผ๋ฏ๋ก ์ฃผ์ํ์ ์ผ ํฉ๋๋ค.
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(toolCallback)
.build();
ChatModel์ ๋๊ตฌ ์ถ๊ฐํ๊ธฐ
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์ ์ง์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ๊ฒฝ์ฐ, FunctionToolCallback ์ธ์คํด์ค๋ฅผ ToolCallingChatOptions์ toolCallbacks() ๋ฉ์๋์ ์ ๋ฌํ์ค ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ์ถ๊ฐ๋ ๋๊ตฌ๋ ํด๋น ์ฑํ ์์ฒญ์๋ง ์ฌ์ฉ๋ฉ๋๋ค.
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build():
Prompt prompt = new Prompt("What's the weather like in Copenhagen?", chatOptions);
chatModel.call(prompt);
ChatModel์ ๊ธฐ๋ณธ ๋๊ตฌ ์ถ๊ฐํ๊ธฐ
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์ ์ง์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ๊ฒฝ์ฐ, ChatModel์ ์์ฑํ ๋ ์ฌ์ฉ๋๋ ToolCallingChatOptions ์ธ์คํด์ค์ toolCallbacks() ๋ฉ์๋์ FunctionToolCallback ์ธ์คํด์ค๋ฅผ ์ ๋ฌํ์ฌ ๊ธฐ๋ณธ ๋๊ตฌ๋ก ์ถ๊ฐํ์ค ์ ์์ต๋๋ค. ๊ธฐ๋ณธ ๋๊ตฌ์ ๋ฐํ์ ๋๊ตฌ๊ฐ ๋ชจ๋ ์ ๊ณต๋๋ ๊ฒฝ์ฐ, ๋ฐํ์ ๋๊ตฌ๊ฐ ๊ธฐ๋ณธ ๋๊ตฌ๋ฅผ ์์ ํ ๋ฎ์ด์๋๋ค.
์ฃผ์
๊ธฐ๋ณธ ๋๊ตฌ๋ ํด๋น ChatModel ์ธ์คํด์ค์์ ์ํ๋๋ ๋ชจ๋ ์ฑํ ์์ฒญ์ ๊ฑธ์ณ ๊ณต์ ๋ฉ๋๋ค. ์ฌ๋ฌ ์์ฒญ์์ ๊ณตํต์ ์ผ๋ก ์ฌ์ฉ๋๋ ๋๊ตฌ๋ฅผ ์ ์ํ ๋ ์ ์ฉํ์ง๋ง, ์ฃผ์ํ์ง ์์ผ๋ฉด ์์น ์๋ ์ํฉ์์๋ ๋๊ตฌ๊ฐ ๋ ธ์ถ๋ ์ ์์ผ๋ฏ๋ก ์ ์คํ๊ฒ ์ฌ์ฉํ์ ์ผ ํฉ๋๋ค.
ToolCallback toolCallback = ...
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.build())
.build();
๋์ ์ง์ ๋ฐฉ์: @Bean
๋๊ตฌ๋ฅผ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ์ง์ ํ๋ ๋์ , Spring ๋น์ผ๋ก ์ ์ํ๊ณ ToolCallbackResolver ์ธํฐํ์ด์ค(๊ตฌํ์ฒด: SpringBeanToolCallbackResolver)๋ฅผ ํตํด Spring AI๊ฐ ๋ฐํ์์ ์ด๋ฅผ ๋์ ์ผ๋ก ํด์ํ๋๋ก ํ์ค ์ ์์ต๋๋ค. ์ด ๋ฐฉ์์ Function, Supplier, Consumer, BiFunction ํ์ ์ ๋น์ ๋๊ตฌ๋ก ์ฌ์ฉํ ์ ์๋ ์ ์ฐํจ์ ์ ๊ณตํฉ๋๋ค. ๋น ์ด๋ฆ์ ๋๊ตฌ ์ด๋ฆ์ผ๋ก ์ฌ์ฉ๋๋ฉฐ, Spring Framework์ @Description ์ ๋ ธํ ์ด์ ์ ํตํด ๋๊ตฌ์ ๋ํ ์ค๋ช ์ ์ ๊ณตํ์ค ์ ์์ต๋๋ค. ์ด ์ค๋ช ์ ๋ชจ๋ธ์ด ํด๋น ๋๊ตฌ๋ฅผ ์ธ์ , ์ด๋ป๊ฒ ํธ์ถํด์ผ ํ๋์ง๋ฅผ ์ดํดํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ์ค๋ช ์ ์ ๊ณตํ์ง ์์ผ๋ฉด ๋ฉ์๋ ์ด๋ฆ์ด ๋๊ตฌ ์ค๋ช ์ผ๋ก ์ฌ์ฉ๋์ง๋ง, ๋ชจ๋ธ์ด ๋๊ตฌ์ ๋ชฉ์ ๊ณผ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ๋ช ํํ ์ดํดํ ์ ์๋๋ก ์์ธํ ์ค๋ช ์ ์ ๊ณตํ๋ ๊ฒ์ด ๊ฐ๋ ฅํ ๊ถ์ฅ๋ฉ๋๋ค. ์ค๋ช ์ด ๋ถ์คํ ๊ฒฝ์ฐ, ๋ชจ๋ธ์ด ๋๊ตฌ๋ฅผ ์ ์ ํ ์ฌ์ฉํ์ง ์๊ฑฐ๋ ์ค์ฉํ ์ ์์ต๋๋ค.
@Configuration(proxyBeanMethods = false)
class WeatherTools {
WeatherService weatherService = new WeatherService();
@Bean
@Description("Get the weather in location")
Function<WeatherRequest, WeatherResponse> currentWeather() {
return weatherService;
}
}
์ฐธ๊ณ
์ผ๋ถ ํ์ ์ ์ง์๋์ง ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์ Function Tool Limitations ํญ๋ชฉ์ ์ฐธ์กฐํ์ธ์.
๋๊ตฌ์ ์ ๋ ฅ ๋งค๊ฐ๋ณ์์ ๋ํ JSON ์คํค๋ง๋ ์๋์ผ๋ก ์์ฑ๋ฉ๋๋ค. ์ ๋ ฅ ๋งค๊ฐ๋ณ์์ ๋ํ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ ค๋ฉด @ToolParam ์ ๋ ธํ ์ด์ ์ ์ฌ์ฉํ์ค ์ ์์ผ๋ฉฐ, ์ด๋ฅผ ํตํด ์ค๋ช ์ ์ถ๊ฐํ๊ฑฐ๋ ํด๋น ๋งค๊ฐ๋ณ์๊ฐ ํ์์ธ์ง ์ ํ์ฌํญ์ธ์ง ์ง์ ํ์ค ์ ์์ต๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ ์ ๋ ฅ ๋งค๊ฐ๋ณ์๋ ํ์๋ก ๊ฐ์ฃผ๋ฉ๋๋ค. ์์ธํ ๋ด์ฉ์ JSON Schema ํญ๋ชฉ์ ์ฐธ์กฐํ์๊ธฐ ๋ฐ๋๋๋ค.
record WeatherRequest(@ToolParam(description = "The name of a city or a country") String location, Unit unit) {}
์ด ๋๊ตฌ ์ง์ ๋ฐฉ์์ ๋๊ตฌ๊ฐ ๋ฐํ์์ ํด์๋๊ธฐ ๋๋ฌธ์ ํ์ ์์ ์ฑ์ด ๋ณด์ฅ๋์ง ์๋๋ค๋ ๋จ์ ์ด ์์ต๋๋ค. ์ด๋ฅผ ์ํํ๊ธฐ ์ํด, @Bean ์ ๋ ธํ ์ด์ ์ ์ฌ์ฉํ ๋ ๋๊ตฌ ์ด๋ฆ์ ๋ช ์์ ์ผ๋ก ์ง์ ํ๊ณ ํด๋น ๊ฐ์ ์์๋ก ์ ์ฅํจ์ผ๋ก์จ, ์ฑํ ์์ฒญ ์ ํ๋์ฝ๋ฉ๋ ๋ฌธ์์ด ๋์ ํด๋น ์์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
@Configuration(proxyBeanMethods = false)
class WeatherTools {
public static final String CURRENT_WEATHER_TOOL = "currentWeather";
@Bean(CURRENT_WEATHER_TOOL)
@Description("Get the weather in location")
Function<WeatherRequest, WeatherResponse> currentWeather() {
...
}
}
ChatClient์ ๋๊ตฌ ์ถ๊ฐํ๊ธฐ
๋์ ์ง์ ๋ฐฉ์์ ์ฌ์ฉํ ๊ฒฝ์ฐ, ๋๊ตฌ ์ด๋ฆ(์ฆ, ํจ์ํ ๋น์ ์ด๋ฆ)์ ChatClient์ tools() ๋ฉ์๋์ ์ ๋ฌํ์ค ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ์ถ๊ฐ๋ ๋๊ตฌ๋ ํด๋น ์ฑํ ์์ฒญ์๋ง ์ฌ์ฉ๋ฉ๋๋ค.
ChatClient.create(chatModel)
.prompt("What's the weather like in Copenhagen?")
.tools("currentWeather")
.call()
.content();
ChatClient์ ๊ธฐ๋ณธ ๋๊ตฌ ์ถ๊ฐํ๊ธฐ
๋์ ์ง์ ๋ฐฉ์์ ์ฌ์ฉํ ๊ฒฝ์ฐ, ๋๊ตฌ ์ด๋ฆ์ ChatClient.Builder์ defaultTools() ๋ฉ์๋์ ์ ๋ฌํ์ฌ ๊ธฐ๋ณธ ๋๊ตฌ๋ก ์ถ๊ฐํ์ค ์ ์์ต๋๋ค. ๊ธฐ๋ณธ ๋๊ตฌ์ ๋ฐํ์ ๋๊ตฌ๊ฐ ๋ชจ๋ ์ ๊ณต๋๋ ๊ฒฝ์ฐ, ๋ฐํ์ ๋๊ตฌ๊ฐ ๊ธฐ๋ณธ ๋๊ตฌ๋ฅผ ์์ ํ ๋ฎ์ด์๋๋ค.
์ฃผ์
๊ธฐ๋ณธ ๋๊ตฌ๋ ๋์ผํ ChatClient.Builder๋ก ์์ฑ๋ ๋ชจ๋ ChatClient ์ธ์คํด์ค์์ ์ํ๋๋ ๋ชจ๋ ์ฑํ ์์ฒญ์ ๊ฑธ์ณ ๊ณต์ ๋ฉ๋๋ค. ์ด๋ฌํ ๋๊ตฌ๋ ์ฌ๋ฌ ์ฑํ ์์ฒญ์์ ๊ณตํต์ ์ผ๋ก ์ฌ์ฉ๋๋ ๋๊ตฌ๋ฅผ ์ ์ํ ๋ ์ ์ฉํ์ง๋ง, ์ฃผ์ํ์ง ์์ผ๋ฉด ์์น ์๋ ์ํฉ์์๋ ๋๊ตฌ๊ฐ ๋ ธ์ถ๋ ์ ์์ผ๋ฏ๋ก ์ ์คํ๊ฒ ์ฌ์ฉํ์ ์ผ ํฉ๋๋ค.
ChatModel chatModel = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools("currentWeather")
.build();
ChatModel์ ๋๊ตฌ ์ถ๊ฐํ๊ธฐ
๋์ ์ง์ ๋ฐฉ์์ ์ฌ์ฉํ ๊ฒฝ์ฐ, ChatModel์ ํธ์ถํ ๋ ์ฌ์ฉํ๋ ToolCallingChatOptions์ toolNames() ๋ฉ์๋์ ๋๊ตฌ ์ด๋ฆ์ ์ ๋ฌํ์ค ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ์ถ๊ฐ๋ ๋๊ตฌ๋ ํด๋น ์ฑํ ์์ฒญ์๋ง ์ฌ์ฉ๋ฉ๋๋ค.
ChatModel chatModel = ...
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolNames("currentWeather")
.build():
Prompt prompt = new Prompt("What's the weather like in Copenhagen?", chatOptions);
chatModel.call(prompt);
ChatModel์ ๊ธฐ๋ณธ ๋๊ตฌ ์ถ๊ฐํ๊ธฐ
๋์ ์ง์ ๋ฐฉ์์ ์ฌ์ฉํ ๊ฒฝ์ฐ, ChatModel์ ์์ฑํ ๋ ์ฌ์ฉ๋๋ ToolCallingChatOptions ์ธ์คํด์ค์ toolNames() ๋ฉ์๋์ ๋๊ตฌ ์ด๋ฆ์ ์ ๋ฌํ์ฌ ๊ธฐ๋ณธ ๋๊ตฌ๋ก ์ถ๊ฐํ์ค ์ ์์ต๋๋ค. ๊ธฐ๋ณธ ๋๊ตฌ์ ๋ฐํ์ ๋๊ตฌ๊ฐ ๋ชจ๋ ์ ๊ณต๋๋ ๊ฒฝ์ฐ, ๋ฐํ์ ๋๊ตฌ๊ฐ ๊ธฐ๋ณธ ๋๊ตฌ๋ฅผ ์์ ํ ๋ฎ์ด์๋๋ค.
์ฃผ์
๊ธฐ๋ณธ ๋๊ตฌ๋ ํด๋น ChatModel ์ธ์คํด์ค์์ ์ํ๋๋ ๋ชจ๋ ์ฑํ ์์ฒญ์ ๊ฑธ์ณ ๊ณต์ ๋ฉ๋๋ค. ์ด๋ฌํ ๋๊ตฌ๋ ์ฌ๋ฌ ์์ฒญ์์ ๊ณตํต์ ์ผ๋ก ์ฌ์ฉ๋๋ ๋๊ตฌ๋ฅผ ์ ์ํ ๋ ์ ์ฉํ์ง๋ง, ์ฃผ์ํด์ ์ฌ์ฉํ์ง ์์ผ๋ฉด ์๋ํ์ง ์์ ์ํฉ์์๋ ๋๊ตฌ๊ฐ ๋ ธ์ถ๋ ์ ์์ผ๋ฏ๋ก ์ ์คํ๊ฒ ์ฌ์ฉํ์ ์ผ ํฉ๋๋ค.
ChatModel chatModel = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().build())
.defaultOptions(ToolCallingChatOptions.builder()
.toolNames("currentWeather")
.build())
.build();
ํจ์ํ ๋๊ตฌ(Function Tool) ์ ํ ์ฌํญ
๋๊ตฌ๋ก ์ฌ์ฉ๋๋ ํจ์์ ์ ๋ ฅ ๋๋ ์ถ๋ ฅ ํ์ ์ผ๋ก ํ์ฌ ์ง์๋์ง ์๋ ํ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ๊ธฐ๋ณธํ(Primitive types)
- Optional
- ์ปฌ๋ ์ ํ์ (List, Map, Array, Set ๋ฑ)
- ๋น๋๊ธฐ ํ์ (CompletableFuture, Future ๋ฑ)
- ๋ฆฌ์กํฐ๋ธ ํ์ (Flow, Mono, Flux ๋ฑ)
๊ธฐ๋ณธํ๊ณผ ์ปฌ๋ ์ ํ์ ์ ๋ฉ์๋ ๊ธฐ๋ฐ ๋๊ตฌ ์ง์ ๋ฐฉ์(Method-based tool specification approach)์ ํตํด ์ง์๋ฉ๋๋ค. ์์ธํ ๋ด์ฉ์ Methods as Tools ํญ๋ชฉ์ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.
๋๊ตฌ ๋ช ์ธ
Spring AI์์๋ ๋๊ตฌ๊ฐ ToolCallback ์ธํฐํ์ด์ค๋ฅผ ํตํด ๋ชจ๋ธ๋ง ๋ฉ๋๋ค. ์์ ์ค๋ช ํ ์น์ ๋ค์์๋ Spring AI์์ ์ ๊ณตํ๋ ๊ธฐ๋ณธ ๊ธฐ๋ฅ์ ์ฌ์ฉํด ๋ฉ์๋๋ ํจ์๋ก ๋๊ตฌ๋ฅผ ์ ์ํ๋ ๋ฐฉ๋ฒ์ ์ดํด๋ณด์์ต๋๋ค (์์ธํ ๋ด์ฉ์ Methods as Tools ๋ฐ Functions as Tools ์ฐธ๊ณ ). ์ด ์น์ ์์๋ ๋๊ตฌ ๋ช ์ธ์ ๋ํด ๋ ๊น์ด ๋ค๋ฃจ๊ณ , ๋ ๋ค์ํ ์ฌ์ฉ ์ฌ๋ก๋ฅผ ์ง์ํ๊ธฐ ์ํ ์ฌ์ฉ์ ์ ์ ๋ฐ ํ์ฅ ๋ฐฉ๋ฒ์ ์ค๋ช ํฉ๋๋ค.
๋๊ตฌ ์ฝ๋ฐฑ
ToolCallback ์ธํฐํ์ด์ค๋ AI ๋ชจ๋ธ์ด ํธ์ถํ ์ ์๋ ๋๊ตฌ๋ฅผ ์ ์ํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค. ์ฌ๊ธฐ์๋ ๋๊ตฌ์ ์ ์์ ์คํ ๋ก์ง์ด ๋ชจ๋ ํฌํจ๋ฉ๋๋ค. ๋๊ตฌ๋ฅผ ์ฒ์๋ถํฐ ์ง์ ์ ์ํ๊ณ ์ ํ ๋ ๊ตฌํํด์ผ ํ๋ ํต์ฌ ์ธํฐํ์ด์ค์ ๋๋ค. ์๋ฅผ ๋ค์ด, MCP ํด๋ผ์ด์ธํธ(Model Context Protocol ์ฌ์ฉ)๋ ChatClient๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ๋ชจ๋ํ ์์ด์ ํธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ ๋ ToolCallback์ ๊ตฌํํ์ฌ ๋๊ตฌ๋ฅผ ์ ์ํ ์ ์์ต๋๋ค.
์ด ์ธํฐํ์ด์ค๋ ๋ค์๊ณผ ๊ฐ์ ๋ฉ์๋๋ค์ ์ ๊ณตํฉ๋๋ค:
public interface ToolCallback {
/**
* AI ๋ชจ๋ธ์ด ๋๊ตฌ๋ฅผ ์ธ์ , ์ด๋ป๊ฒ ํธ์ถํ ์ง๋ฅผ ํ๋จํ๋ ๋ฐ ์ฌ์ฉํ๋ ๋๊ตฌ ์ ์๋ฅผ ๋ฐํํฉ๋๋ค.
*/
ToolDefinition getToolDefinition();
/**
* ๋๊ตฌ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ํ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ๋ฐํํฉ๋๋ค.
*/
ToolMetadata getToolMetadata();
/**
* ์ฃผ์ด์ง ์
๋ ฅ์ ์ฌ์ฉํด ๋๊ตฌ๋ฅผ ์คํํ๊ณ , AI ๋ชจ๋ธ์ ์ ๋ฌํ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํฉ๋๋ค.
*/
String call(String toolInput);
/**
* ์ฃผ์ด์ง ์
๋ ฅ๊ณผ ์ปจํ
์คํธ๋ฅผ ์ฌ์ฉํด ๋๊ตฌ๋ฅผ ์คํํ๊ณ , AI ๋ชจ๋ธ์ ์ ๋ฌํ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํฉ๋๋ค.
*/
String call(String toolInput, ToolContext toolContext);
}
Spring AI๋ ๋๊ตฌ ๋ฉ์๋์ ๋ํ MethodToolCallback๊ณผ ๋๊ตฌ ํจ์์ ๋ํ FunctionToolCallback์ ๊ธฐ๋ณธ์ ์ผ๋ก ๊ตฌํํ์ฌ ์ ๊ณตํฉ๋๋ค.
๋๊ตฌ ์ ์
ToolDefinition ์ธํฐํ์ด์ค๋ ๋๊ตฌ์ ์ด๋ฆ, ์ค๋ช , ์ ๋ ฅ ์คํค๋ง ๋ฑ AI ๋ชจ๋ธ์ด ํด๋น ๋๊ตฌ์ ์ฌ์ฉ ๊ฐ๋ฅ ์ฌ๋ถ๋ฅผ ์ธ์ํ๋ ๋ฐ ํ์ํ ์ ๋ณด๋ฅผ ์ ๊ณตํฉ๋๋ค. ๊ฐ ToolCallback ๊ตฌํ์ฒด๋ ๋๊ตฌ๋ฅผ ์ ์ํ๊ธฐ ์ํด ๋ฐ๋์ ToolDefinition ์ธ์คํด์ค๋ฅผ ์ ๊ณตํด์ผ ํฉ๋๋ค.
์ด ์ธํฐํ์ด์ค๋ ๋ค์๊ณผ ๊ฐ์ ๋ฉ์๋๋ฅผ ์ ๊ณตํฉ๋๋ค:
public interface ToolDefinition {
/**
* ๋๊ตฌ์ ์ด๋ฆ. ๋ชจ๋ธ์ ์ ๊ณต๋๋ ๋๊ตฌ ์งํฉ ๋ด์์ ๊ณ ์ ํด์ผ ํฉ๋๋ค.
*/
String name();
/**
* ๋๊ตฌ์ ์ค๋ช
. AI ๋ชจ๋ธ์ด ๋๊ตฌ์ ๊ธฐ๋ฅ์ ํ์
ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
*/
String description();
/**
* ๋๊ตฌ ํธ์ถ์ ์ฌ์ฉ๋๋ ๋งค๊ฐ๋ณ์๋ค์ ์คํค๋ง.
*/
String inputSchema();
}
์ฐธ๊ณ
์ ๋ ฅ ์คํค๋ง์ ๋ํ ์์ธํ ๋ด์ฉ์ JSON Schema ํญ๋ชฉ์ ์ฐธ๊ณ ํ์ญ์์ค.
ToolDefinition.Builder๋ฅผ ์ฌ์ฉํ๋ฉด ๊ธฐ๋ณธ ๊ตฌํ์ฒด์ธ DefaultToolDefinition์ ์ด์ฉํ์ฌ ToolDefinition ์ธ์คํด์ค๋ฅผ ์์ฑํ์ค ์ ์์ต๋๋ค.
ToolDefinition toolDefinition = ToolDefinition.builder()
.name("currentWeather")
.description("Get the weather in location")
.inputSchema("""
{
"type": "object",
"properties": {
"location": {
"type": "string"
},
"unit": {
"type": "string",
"enum": ["C", "F"]
}
},
"required": ["location", "unit"]
}
""")
.build();
๋ฉ์๋ ๊ธฐ๋ฐ ๋๊ตฌ ์ ์
๋ฉ์๋๋ก ๋๊ตฌ๋ฅผ ๊ตฌ์ฑํ ๋๋ ToolDefinition์ด ์๋์ผ๋ก ์์ฑ๋ฉ๋๋ค. ์ง์ ToolDefinition์ ์์ฑํ๊ณ ์ถ๋ค๋ฉด, ํธ๋ฆฌํ ๋น๋(ToolDefinition.Builder)๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolDefinition toolDefinition = ToolDefinition.from(method);
๋ฉ์๋๋ก๋ถํฐ ์์ฑ๋ ToolDefinition์๋ ๋ฉ์๋ ์ด๋ฆ์ด ๋๊ตฌ ์ด๋ฆ๊ณผ ์ค๋ช ์ผ๋ก ์ฌ์ฉ๋๋ฉฐ, ๋ฉ์๋์ ์ ๋ ฅ ํ๋ผ๋ฏธํฐ์ ๋ํ JSON ์คํค๋ง๊ฐ ํฌํจ๋ฉ๋๋ค.
๋ฉ์๋์ @Tool ์ด๋ ธํ ์ด์ ์ด ์ง์ ๋์ด ์๊ณ ์ด๋ฆ์ด๋ ์ค๋ช ์ด ์ค์ ๋์ด ์๋ ๊ฒฝ์ฐ, ํด๋น ์ด๋ ธํ ์ด์ ์ ๊ฐ์ ์ฐ์ ์ ์ผ๋ก ์ฌ์ฉํฉ๋๋ค.
์ฐธ๊ณ
์์ธํ ๋ด์ฉ์ Methods as Tools ํญ๋ชฉ์ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.
์ผ๋ถ ๋๋ ๋ชจ๋ ์์ฑ์ ๋ช ์์ ์ผ๋ก ์ง์ ํ๊ณ ์ ํ๋ ๊ฒฝ์ฐ, ToolDefinition.Builder๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์ ์ ์ ToolDefinition ์ธ์คํด์ค๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolDefinition toolDefinition = ToolDefinition.builder(method)
.name("currentDateTime")
.description("Get the current date and time in the user's timezone")
.inputSchema(JsonSchemaGenerator.generateForMethodInput(method))
.build();
ํจ์ ๋๊ตฌ ์ ์
ํจ์๋ก ๋๊ตฌ๋ฅผ ๊ตฌ์ฑํ ๋๋ ToolDefinition์ด ์๋์ผ๋ก ์์ฑ๋ฉ๋๋ค.
FunctionToolCallback.Builder๋ฅผ ์ฌ์ฉํ์ฌ FunctionToolCallback ์ธ์คํด์ค๋ฅผ ์์ฑํ ๋, ๋๊ตฌ ์ด๋ฆ, ์ค๋ช , ์ ๋ ฅ ์คํค๋ง๋ฅผ ์ง์ ์ง์ ํ ์ ์์ผ๋ฉฐ, ์ด ๊ฐ๋ค์ ๊ธฐ๋ฐ์ผ๋ก ToolDefinition์ด ์์ฑ๋ฉ๋๋ค.
์์ธํ ๋ด์ฉ์ Functions as Tools ํญ๋ชฉ์ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.
JSON ์คํค๋ง
AI ๋ชจ๋ธ์ ๋๊ตฌ๋ฅผ ์ ๊ณตํ ๋, ํด๋น ๋๊ตฌ๋ฅผ ํธ์ถํ๊ธฐ ์ํ ์ ๋ ฅ ํ์ ์ ์คํค๋ง๋ฅผ ๋ชจ๋ธ์ด ์์์ผ ํฉ๋๋ค. ์ด ์คํค๋ง๋ ๋๊ตฌ๋ฅผ ์ด๋ป๊ฒ ํธ์ถํ ์ง ์ดํดํ๊ณ ์์ฒญ์ ์ค๋นํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. Spring AI๋ JsonSchemaGenerator ํด๋์ค๋ฅผ ํตํด ๋๊ตฌ์ ์ ๋ ฅ ํ์ ์ ๋ํ JSON ์คํค๋ง๋ฅผ ์๋์ผ๋ก ์์ฑํ๋ ๊ธฐ๋ฅ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํฉ๋๋ค. ์์ฑ๋ ์คํค๋ง๋ ToolDefinition์ ์ผ๋ถ๋ก ํฌํจ๋ฉ๋๋ค.
์ฐธ๊ณ
ToolDefinition ๋ฐ ์ ๋ ฅ ์คํค๋ง๋ฅผ ์ ๋ฌํ๋ ๋ฐฉ๋ฒ์ ๋ํ ์์ธํ ๋ด์ฉ์ Tool Definition ํญ๋ชฉ์ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.
JsonSchemaGenerator ํด๋์ค๋ ๋ด๋ถ์ ์ผ๋ก ๋ฉ์๋๋ ํจ์์ ์ ๋ ฅ ๋งค๊ฐ๋ณ์์ ๋ํ JSON ์คํค๋ง๋ฅผ ์์ฑํ๋ ๋ฐ ์ฌ์ฉ๋๋ฉฐ, ์ด๋ “Methods as Tools” ๋ฐ “Functions as Tools”์์ ์ค๋ช ๋ ์ ๋ต๋ค์ ๋ฐ๋ฆ ๋๋ค. JSON ์คํค๋ง ์์ฑ ๋ก์ง์ ๋ฉ์๋ ๋ฐ ํจ์์ ์ ๋ ฅ ๋งค๊ฐ๋ณ์์ ๋ค์ํ ์ ๋ ธํ ์ด์ ์ ์ฌ์ฉํ์ฌ ์์ฑ ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฉ์ ์ ์ํ ์ ์๋๋ก ์ง์ํฉ๋๋ค.
์ด ์น์ ์์๋ ๋๊ตฌ์ ์ ๋ ฅ ๋งค๊ฐ๋ณ์์ ๋ํ JSON ์คํค๋ง๋ฅผ ์์ฑํ ๋ ์ฌ์ฉ์ ์ ์ํ ์ ์๋ ๋ ๊ฐ์ง ์ฃผ์ ์ต์ ์ธ ์ค๋ช (description)๊ณผ ํ์ ์ฌ๋ถ(required status)์ ๋ํด ์ค๋ช ํฉ๋๋ค.
์ค๋ช
๋๊ตฌ ์์ฒด์ ๋ํ ์ค๋ช ์ธ์๋, ๋๊ตฌ์ ์ ๋ ฅ ๋งค๊ฐ๋ณ์์ ๋ํด์๋ ์ค๋ช ์ ์ ๊ณตํ์ค ์ ์์ต๋๋ค. ์ด ์ค๋ช ์ ๋งค๊ฐ๋ณ์๊ฐ ์ด๋ค ํ์์ด์ด์ผ ํ๋์ง, ํ์ฉ๋๋ ๊ฐ์ ๋ฌด์์ธ์ง ๋ฑ ํต์ฌ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ์ด๋ ๋ชจ๋ธ์ด ์ ๋ ฅ ์คํค๋ง๋ฅผ ์ดํดํ๊ณ ์ฌ๋ฐ๋ฅด๊ฒ ์ฌ์ฉํ๋ ๋ฐ ๋งค์ฐ ์ ์ฉํฉ๋๋ค. Spring AI๋ ๋ค์ ์ ๋ ธํ ์ด์ ์ค ํ๋๋ฅผ ์ฌ์ฉํ์ฌ ์ ๋ ฅ ๋งค๊ฐ๋ณ์์ ๋ํ ์ค๋ช ์ ์์ฑํ๋ ๊ธฐ๋ฅ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ง์ํฉ๋๋ค:
- @ToolParam(description = "...") — Spring AI ์ ๊ณต
- @JsonClassDescription(description = "...") — Jackson ์ ๊ณต
- @JsonPropertyDescription(description = "...") — Jackson ์ ๊ณต
- @Schema(description = "...") — Swagger(OpenAPI) ์ ๊ณต
์ด ๋ฐฉ์์ ๋ฉ์๋์ ํจ์ ๋ชจ๋์ ์ ์ฉ๋๋ฉฐ, ์ค์ฒฉ๋ ํ์ (nested types)์ ๋ํด์๋ ์ฌ๊ท์ ์ผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.context.i18n.LocaleContextHolder;
class DateTimeTools {
@Tool(description = "Set a user alarm for the given time")
void setAlarm(@ToolParam(description = "Time in ISO-8601 format") String time) {
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
System.out.println("Alarm set for " + alarmTime);
}
}
ํ์ ์ฌ๋ถ
๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ ์ ๋ ฅ ๋งค๊ฐ๋ณ์๋ ํ์(required)๋ก ๊ฐ์ฃผ๋๋ฉฐ, ์ด๋ก ์ธํด AI ๋ชจ๋ธ์ ๋๊ตฌ๋ฅผ ํธ์ถํ ๋ ํด๋น ๋งค๊ฐ๋ณ์์ ๊ฐ์ ๋ฐ๋์ ์ ๊ณตํด์ผ ํฉ๋๋ค. ๊ทธ๋ฌ๋ ๋ค์์ ์ ๋ ธํ ์ด์ ์ค ํ๋๋ฅผ ์ฌ์ฉํ๋ฉด ์ ๋ ฅ ๋งค๊ฐ๋ณ์๋ฅผ ์ ํ(optional)์ผ๋ก ์ง์ ํ์ค ์ ์์ต๋๋ค. ์ ์ฉ ์ฐ์ ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- @ToolParam(required = false) — Spring AI์์ ์ ๊ณต
- @JsonProperty(required = false) — Jackson์์ ์ ๊ณต
- @Schema(required = false) — Swagger์์ ์ ๊ณต
- @Nullable — Spring Framework์์ ์ ๊ณต
์ด ๋ฐฉ์์ ๋ฉ์๋์ ํจ์ ๋ชจ๋์ ์ ์ฉ๋๋ฉฐ, ์ค์ฒฉ ํ์ (nested types)์๋ ์ฌ๊ท์ ์ผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
class CustomerTools {
@Tool(description = "Update customer information")
void updateCustomerInfo(Long id, String name, @ToolParam(required = false) String email) {
System.out.println("Updated info for customer with id: " + id);
}
}
์ฃผ์
์ ๋ ฅ ๋งค๊ฐ๋ณ์์ ๋ํด ์ฌ๋ฐ๋ฅธ ํ์ ์ฌ๋ถ(required status)๋ฅผ ์ ์ํ๋ ๊ฒ์ ํ๊ฐ(hallucination) ํ์์ ๋ฐฉ์งํ๊ณ , ๋ชจ๋ธ์ด ๋๊ตฌ๋ฅผ ํธ์ถํ ๋ ์ ํํ ์ ๋ ฅ๊ฐ์ ์ ๊ณตํ๋๋ก ํ๊ธฐ ์ํด ๋งค์ฐ ์ค์ํฉ๋๋ค.
์์ ์์ ์์ email ๋งค๊ฐ๋ณ์๋ ์ ํ ํญ๋ชฉ์ด๋ฏ๋ก, ๋ชจ๋ธ์ ํด๋น ๊ฐ ์์ด๋ ๋๊ตฌ๋ฅผ ํธ์ถํ ์ ์์ต๋๋ค. ๋ฐ๋ฉด, ์ด ๋งค๊ฐ๋ณ์๊ฐ ํ์์๋ค๋ฉด ๋ชจ๋ธ์ ๋ฐ๋์ ๊ฐ์ ์ ๊ณตํด์ผ ํ๋ฉฐ, ๊ฐ์ด ์กด์ฌํ์ง ์๋ ๊ฒฝ์ฐ์๋ ์์์ ๊ฐ์ ๋ง๋ค์ด๋ด์ด ์๋ชป๋ ํธ์ถ๋ก ์ด์ด์ง ์ ์์ต๋๋ค.
๊ฒฐ๊ณผ ๋ณํ (Result Conversion)
๋๊ตฌ ํธ์ถ์ ๊ฒฐ๊ณผ๋ ToolCallResultConverter๋ฅผ ์ฌ์ฉํ์ฌ ๋ฌธ์์ด๋ก ์ง๋ ฌํ๋ ํ AI ๋ชจ๋ธ์ ์ ๋ฌ๋ฉ๋๋ค. ToolCallResultConverter ์ธํฐํ์ด์ค๋ ๋๊ตฌ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๋ฌธ์์ด(String) ๊ฐ์ฒด๋ก ๋ณํํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค.
์ด ์ธํฐํ์ด์ค๋ ๋ค์๊ณผ ๊ฐ์ ๋ฉ์๋๋ฅผ ํฌํจํฉ๋๋ค:
@FunctionalInterface
public interface ToolCallResultConverter {
/**
* ๋๊ตฌ์์ ๋ฐํ๋ ๊ฐ์ฒด๋ฅผ ์ฃผ์ด์ง ํด๋์ค ํ์
์ ๋ง๋ ๋ฌธ์์ด๋ก ๋ณํํฉ๋๋ค.
*/
String convert(@Nullable Object result, @Nullable Type returnType);
}
๊ฒฐ๊ณผ๋ ๋ฐ๋์ ์ง๋ ฌํ ๊ฐ๋ฅํ ํ์ ์ด์ด์ผ ํฉ๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก ๊ฒฐ๊ณผ๋ Jackson์ ์ฌ์ฉํ์ฌ JSON ํ์์ผ๋ก ์ง๋ ฌํ๋๋ฉฐ, ์ด๋ DefaultToolCallResultConverter๊ฐ ์ฒ๋ฆฌํฉ๋๋ค. ํ์ง๋ง ํ์์ ๋ฐ๋ผ ์ฌ์ฉ์ ์ ์ ToolCallResultConverter ๊ตฌํ์ฒด๋ฅผ ์ ๊ณตํ์ฌ ์ง๋ ฌํ ๋ฐฉ์์ ์ง์ ์ปค์คํฐ๋ง์ด์งํ์ค ์ ์์ต๋๋ค.
Spring AI๋ ๋ฉ์๋ ๊ธฐ๋ฐ ๋๊ตฌ์ ํจ์ ๊ธฐ๋ฐ ๋๊ตฌ ๋ชจ๋์์ ToolCallResultConverter๋ฅผ ์ฌ์ฉํฉ๋๋ค.
๋ฉ์๋ ๊ธฐ๋ฐ ๋๊ตฌ ํธ์ถ ๊ฒฐ๊ณผ ๋ณํ
์ ์ธํ ๋ฐฉ์์ผ๋ก ๋ฉ์๋๋ฅผ ๋๊ตฌ๋ก ๊ตฌ์ฑํ ๋, @Tool ์ ๋ ธํ ์ด์ ์ resultConverter() ์์ฑ์ ์ค์ ํ์ฌ ํด๋น ๋๊ตฌ์ ์ฌ์ฉํ ์ฌ์ฉ์ ์ ์ ToolCallResultConverter๋ฅผ ์ง์ ํ์ค ์ ์์ต๋๋ค.
class CustomerTools {
@Tool(description = "Retrieve customer information", resultConverter = CustomToolCallResultConverter.class)
Customer getCustomerInfo(Long id) {
return customerRepository.findById(id);
}
}
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ๋๊ตฌ๋ฅผ ๊ตฌ์ฑํ๋ ๊ฒฝ์ฐ, MethodToolCallback.Builder์ resultConverter() ์์ฑ์ ์ค์ ํ์ฌ ํด๋น ๋๊ตฌ์ ์ฌ์ฉํ ์ฌ์ฉ์ ์ ์ ToolCallResultConverter๋ฅผ ์ง์ ํ์ค ์ ์์ต๋๋ค.
์์ธํ ๋ด์ฉ์ Methods as Tools ํญ๋ชฉ์ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.
ํจ์ ๊ธฐ๋ฐ ๋๊ตฌ ํธ์ถ ๊ฒฐ๊ณผ ๋ณํ
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ํจ์๋ฅผ ๋๊ตฌ๋ก ๊ตฌ์ฑํ ๋๋ FunctionToolCallback.Builder์ resultConverter() ์์ฑ์ ์ค์ ํ์ฌ ํด๋น ๋๊ตฌ์ ์ฌ์ฉํ ์ฌ์ฉ์ ์ ์ ToolCallResultConverter๋ฅผ ์ง์ ํ์ค ์ ์์ต๋๋ค.
์์ธํ ๋ด์ฉ์ Functions as Tools ํญ๋ชฉ์ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.
๋๊ตฌ ์ปจํ ์คํธ (Tool Context)
Spring AI๋ ToolContext API๋ฅผ ํตํด ๋๊ตฌ์ ์ถ๊ฐ์ ์ธ ์ปจํ ์คํธ ์ ๋ณด๋ฅผ ์ ๋ฌํ๋ ๊ธฐ๋ฅ์ ์ง์ํฉ๋๋ค. ์ด ๊ธฐ๋ฅ์ ํ์ฉํ๋ฉด, AI ๋ชจ๋ธ์ด ์ ๋ฌํ๋ ๋๊ตฌ ์ธ์ ์ธ์๋ ์ฌ์ฉ์ ์ ์ ๋ฐ์ดํฐ๋ฅผ ํจ๊ป ์ ๊ณตํ์ฌ ๋๊ตฌ ์คํ ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
class CustomerTools {
@Tool(description = "Retrieve customer information")
Customer getCustomerInfo(Long id, ToolContext toolContext) {
return customerRepository.findById(id, toolContext.get("tenantId"));
}
}
ToolContext๋ ์ฌ์ฉ์๊ฐ ChatClient๋ฅผ ํธ์ถํ ๋ ์ ๊ณตํ ๋ฐ์ดํฐ๋ก ์ฑ์์ง๋๋ค.
ChatModel chatModel = ...
String response = ChatClient.create(chatModel)
.prompt("Tell me more about the customer with ID 42")
.tools(new CustomerTools())
.toolContext(Map.of("tenantId", "acme"))
.call()
.content();
System.out.println(response);
์ฐธ๊ณ
ToolContext์ ์ ๊ณต๋ ๋ฐ์ดํฐ๋ AI ๋ชจ๋ธ์ ์ ๋ฌ๋์ง ์์ต๋๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก, ChatModel์ ์ง์ ํธ์ถํ ๋์๋ ๋๊ตฌ ์ปจํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ์ ์ํ์ค ์ ์์ต๋๋ค.
ChatModel chatModel = ...
ToolCallback[] customerTools = ToolCallbacks.from(new CustomerTools());
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(customerTools)
.toolContext(Map.of("tenantId", "acme"))
.build();
Prompt prompt = new Prompt("Tell me more about the customer with ID 42", chatOptions);
chatModel.call(prompt);
toolContext ์ต์ ์ด ๊ธฐ๋ณธ ์ต์ ๊ณผ ๋ฐํ์ ์ต์ ๋ชจ๋์ ์ค์ ๋ ๊ฒฝ์ฐ, ์ต์ข ์ ์ผ๋ก ์์ฑ๋๋ ToolContext๋ ๋ ์ต์ ์ ๋ณํฉํ ๊ฐ์ด ๋๋ฉฐ, ์ด๋ ๋ฐํ์ ์ต์ ์ด ๊ธฐ๋ณธ ์ต์ ๋ณด๋ค ์ฐ์ ํฉ๋๋ค.
์ง์ ๋ฐํ(Return Direct)
๊ธฐ๋ณธ์ ์ผ๋ก ๋๊ตฌ ํธ์ถ์ ๊ฒฐ๊ณผ๋ ๋ชจ๋ธ์ ์๋ต์ผ๋ก ์ ๋ฌ๋๋ฉฐ, ์ดํ ๋ชจ๋ธ์ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ์ด์ฉํด ๋ํ๋ฅผ ์ด์ด๊ฐ๋๋ค.
๊ทธ๋ฌ๋ ๊ฒฝ์ฐ์ ๋ฐ๋ผ, ๊ฒฐ๊ณผ๋ฅผ ๋ชจ๋ธ๋ก ๋ณด๋ด๋ ๋์ ํธ์ถ์์๊ฒ ์ง์ ๋ฐํํ๋ ๊ฒ์ด ๋ ์ ์ ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, RAG ๋๊ตฌ์ ์์กดํ๋ ์์ด์ ํธ๋ฅผ ๊ตฌ์ฑํ๋ ๊ฒฝ์ฐ์๋ ๋ชจ๋ธ์ด ํ์ฒ๋ฆฌ๋ฅผ ํ์ง ์๊ณ ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋๋ก ํธ์ถ์์๊ฒ ์ ๋ฌํ๋ ๊ฒ์ด ๋ฐ๋์งํ ์ ์์ต๋๋ค. ๋๋ ์ผ๋ถ ๋๊ตฌ๋ ์์ด์ ํธ์ ์ถ๋ก ๋ฃจํ๋ฅผ ์ข ๋ฃํ๋ ์ญํ ์ ํ๋๋ก ์ค๊ณ๋ ์๋ ์์ต๋๋ค.
๊ฐ ToolCallback ๊ตฌํ์ฒด๋ ํด๋น ๋๊ตฌ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๋ชจ๋ธ๋ก ์ ๋ฌํ ์ง, ๋๋ ํธ์ถ์์๊ฒ ์ง์ ๋ฐํํ ์ง๋ฅผ ์ ์ํ ์ ์์ต๋๋ค. ๊ธฐ๋ณธ ๋์์ ๋ชจ๋ธ๋ก์ ์ ๋ฌ์ด์ง๋ง, ๋๊ตฌ ๋จ์๋ก ์ด ๋์์ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค.
๋๊ตฌ ์คํ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ด๋ฆฌํ๋ ToolCallingManager๋ ๋๊ตฌ์ ์ค์ ๋ returnDirect ์์ฑ์ ์ฒ๋ฆฌํฉ๋๋ค. ์ด ์์ฑ์ด true๋ก ์ค์ ๋์ด ์์ผ๋ฉด, ๋๊ตฌ ํธ์ถ ๊ฒฐ๊ณผ๋ ๋ชจ๋ธ์ ๊ฑฐ์น์ง ์๊ณ ํธ์ถ์์๊ฒ ์ง์ ๋ฐํ๋ฉ๋๋ค. ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ์๋ ๊ฒฐ๊ณผ๊ฐ ๋ชจ๋ธ๋ก ์ ๋ฌ๋ฉ๋๋ค.
์ฐธ๊ณ
์ฌ๋ฌ ๊ฐ์ ๋๊ตฌ ํธ์ถ์ด ํ ๋ฒ์ ์์ฒญ๋๋ ๊ฒฝ์ฐ, ๋ชจ๋ ๋๊ตฌ์ returnDirect ์์ฑ์ด true๋ก ์ค์ ๋์ด ์์ด์ผ๋ง ๊ฒฐ๊ณผ๊ฐ ํธ์ถ์์๊ฒ ์ง์ ๋ฐํ๋ฉ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด, ๋ชจ๋ ๊ฒฐ๊ณผ๋ ๋ชจ๋ธ๋ก ์ ๋ฌ๋ฉ๋๋ค.
- ๋๊ตฌ๋ฅผ ๋ชจ๋ธ์ด ์ฌ์ฉํ ์ ์๋๋ก ํ๋ ค๋ฉด, ํด๋น ๋๊ตฌ์ ์ ์๋ฅผ ์ฑํ ์์ฒญ์ ํฌํจ์์ผ์ผ ํฉ๋๋ค. ๋๊ตฌ ์คํ ๊ฒฐ๊ณผ๋ฅผ ๋ชจ๋ธ์ด ์๋ ํธ์ถ์์๊ฒ ์ง์ ๋ฐํํ๊ณ ์ ํ ๊ฒฝ์ฐ์๋ returnDirect ์์ฑ์ true๋ก ์ค์ ํฉ๋๋ค.
- ๋ชจ๋ธ์ด ๋๊ตฌ ํธ์ถ์ ๊ฒฐ์ ํ๋ฉด, ๋๊ตฌ ์ด๋ฆ๊ณผ ์ ์๋ ์คํค๋ง์ ๋ฐ๋ผ ๊ตฌ์ฑ๋ ์ ๋ ฅ ๋งค๊ฐ๋ณ์๋ฅผ ํฌํจํ ์๋ต์ ์ ์กํฉ๋๋ค.
- ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๊ตฌ ์ด๋ฆ์ ์ฌ์ฉํ์ฌ ๋๊ตฌ๋ฅผ ์๋ณํ๊ณ , ์ ๊ณต๋ ์ ๋ ฅ๊ฐ์ ๊ธฐ๋ฐ์ผ๋ก ํด๋น ๋๊ตฌ๋ฅผ ์คํํฉ๋๋ค.
- ๋๊ตฌ ํธ์ถ ๊ฒฐ๊ณผ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํด ์ฒ๋ฆฌ๋ฉ๋๋ค.
- ๊ทธ๋ฆฌ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ชจ๋ธ๋ก ๋ณด๋ด์ง ์๊ณ , ํธ์ถ์์๊ฒ ์ง์ ๋ฐํํฉ๋๋ค.
๋ฉ์๋ ์ง์ ๋ฐํ(Method Return Direct)
์ ์ธํ ๋ฐฉ์์ผ๋ก ๋ฉ์๋๋ฅผ ๋๊ตฌ๋ก ๊ตฌ์ฑํ ๋, @Tool ์ ๋ ธํ ์ด์ ์ returnDirect ์์ฑ์ true๋ก ์ค์ ํ๋ฉด ํด๋น ๋๊ตฌ์ ์คํ ๊ฒฐ๊ณผ๋ฅผ ํธ์ถ์์๊ฒ ์ง์ ๋ฐํํ๋๋ก ์ง์ ํ ์ ์์ต๋๋ค.
class CustomerTools {
@Tool(description = "Retrieve customer information", returnDirect = true)
Customer getCustomerInfo(Long id) {
return customerRepository.findById(id);
}
}
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ ์ฌ์ฉํ ๊ฒฝ์ฐ, ToolMetadata ์ธํฐํ์ด์ค๋ฅผ ํตํด returnDirect ์์ฑ์ ์ค์ ํ ๋ค, ์ด๋ฅผ MethodToolCallback.Builder์ ์ ๋ฌํ์ฌ ๋๊ตฌ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ํธ์ถ์์๊ฒ ์ง์ ๋ฐํํ๋๋ก ์ง์ ํ์ค ์ ์์ต๋๋ค.
ToolMetadata toolMetadata = ToolMetadata.builder()
.returnDirect(true)
.build();
์์ธํ ๋ด์ฉ์ Methods as Tools ํญ๋ชฉ์ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.
ํจ์ ์ง์ ๋ฐํ(Function Return Direct)
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ํจ์๋ฅผ ๋๊ตฌ๋ก ๊ตฌ์ฑํ ๋, ToolMetadata ์ธํฐํ์ด์ค๋ฅผ ํตํด returnDirect ์์ฑ์ ์ค์ ํ๊ณ ์ด๋ฅผ FunctionToolCallback.Builder์ ์ ๋ฌํ์ฌ ๋๊ตฌ ๊ฒฐ๊ณผ๋ฅผ ํธ์ถ์์๊ฒ ์ง์ ๋ฐํํ๋๋ก ์ง์ ํ์ค ์ ์์ต๋๋ค.
ToolMetadata toolMetadata = ToolMetadata.builder()
.returnDirect(true)
.build();
์์ธํ ๋ด์ฉ์ Methods as Tools ํญ๋ชฉ์ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.
๋๊ตฌ ์คํ
๋๊ตฌ ์คํ (Tool Execution)์ ๋ชจ๋ธ์ด ์์ฒญํ ๋๊ตฌ์ ์ ๋ ฅ ์ธ์๋ฅผ ์ ๋ฌํ์ฌ ํธ์ถํ๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ ๊ณผ์ ์ ๋๋ค.
์ด ์คํ ๊ณผ์ ์ ToolCallingManager ์ธํฐํ์ด์ค๊ฐ ๋ด๋นํ๋ฉฐ, ๋๊ตฌ ์คํ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
public interface ToolCallingManager {
/**
* ๋ชจ๋ธ์ ๋๊ตฌ ํธ์ถ ์ต์
์์ ๋๊ตฌ ์ ์ ๋ชฉ๋ก์ ํด์ํฉ๋๋ค.
*/
List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions);
/**
* ๋ชจ๋ธ์ด ์์ฒญํ ๋๊ตฌ ํธ์ถ์ ์คํํฉ๋๋ค.
*/
ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse);
}
Spring AI์ Spring Boot Starter๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, ToolCallingManager ์ธํฐํ์ด์ค์ ๊ธฐ๋ณธ ๊ตฌํ์ฒด๋ DefaultToolCallingManager์ด๋ฉฐ ์๋ ๊ตฌ์ฑ๋ฉ๋๋ค. ๋๊ตฌ ์คํ ๋์์ ์ฌ์ฉ์ ์ ์ํ๊ณ ์ถ๋ค๋ฉด, ์ฌ์ฉ์ ์ ์ ToolCallingManager ๋น์ ๋ฑ๋กํ์ฌ ๋์์ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค.
@Bean
ToolCallingManager toolCallingManager() {
return ToolCallingManager.builder().build();
}
๊ธฐ๋ณธ์ ์ผ๋ก Spring AI๋ ๊ฐ ChatModel ๊ตฌํ์ฒด ๋ด๋ถ์์ ๋๊ตฌ ์คํ ์๋ช ์ฃผ๊ธฐ๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค. ๊ทธ๋ฌ๋ ์ํ ๊ฒฝ์ฐ ์ด ๋์์ ๋นํ์ฑํํ๊ณ ์ง์ ๋๊ตฌ ์คํ์ ์ ์ดํ ์๋ ์์ต๋๋ค.
์ด ์น์ ์์๋ ์ด๋ฌํ ๋ ๊ฐ์ง ๋ฐฉ์(์๋ ์ฒ๋ฆฌ vs. ์๋ ์ ์ด)์ ๋ํด ์ค๋ช ํฉ๋๋ค.
ํ๋ ์์ํฌ ์ ์ด ๋๊ตฌ ์คํ (Framework-Controlled Tool Execution)
๊ธฐ๋ณธ ๋์์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, Spring AI๋ ๋ชจ๋ธ๋ก๋ถํฐ ๋ฐ์ํ ๋๊ตฌ ํธ์ถ ์์ฒญ์ ์๋์ผ๋ก ๊ฐ๋ก์ฑ๊ณ , ํด๋น ๋๊ตฌ๋ฅผ ์คํํ ํ ๊ฒฐ๊ณผ๋ฅผ ๋ชจ๋ธ์ ๋ฐํํฉ๋๋ค.
์ด ๋ชจ๋ ๊ณผ์ ์ ๊ฐ ChatModel ๊ตฌํ์ฒด ๋ด๋ถ์์ ToolCallingManager๋ฅผ ํตํด ์๋์ผ๋ก ์ฒ๋ฆฌ๋๋ฉฐ, ์ฌ์ฉ์๋ ๋ณ๋๋ก ๊ฐ์ ํ ํ์๊ฐ ์์ต๋๋ค.
- ๋๊ตฌ๋ฅผ ๋ชจ๋ธ์์ ์ฌ์ฉํ ์ ์๋๋ก ํ๋ ค๋ฉด, ํด๋น ๋๊ตฌ์ ์ ์๋ฅผ ์ฑํ ์์ฒญ(Prompt)์ ํฌํจ์ํค๊ณ ChatModel API๋ฅผ ํธ์ถํ์ฌ AI ๋ชจ๋ธ๋ก ์์ฒญ์ ์ ์กํฉ๋๋ค.
- ๋ชจ๋ธ์ด ๋๊ตฌ๋ฅผ ํธ์ถํ๊ธฐ๋ก ๊ฒฐ์ ํ๋ฉด, ์ ์๋ ์คํค๋ง์ ๋ฐ๋ผ ๋๊ตฌ ์ด๋ฆ๊ณผ ์ ๋ ฅ ํ๋ผ๋ฏธํฐ๋ฅผ ํฌํจํ ์๋ต(ChatResponse)์ ์ ์กํฉ๋๋ค.
- ChatModel์ ์ด ๋๊ตฌ ํธ์ถ ์์ฒญ์ ToolCallingManager API๋ก ์ ๋ฌํฉ๋๋ค.
- ToolCallingManager๋ ์ด๋ค ๋๊ตฌ๋ฅผ ํธ์ถํ ์ง ์๋ณํ๊ณ , ์ฃผ์ด์ง ์ ๋ ฅ ํ๋ผ๋ฏธํฐ๋ก ๋๊ตฌ๋ฅผ ์คํํฉ๋๋ค.
- ๋๊ตฌ ์คํ ๊ฒฐ๊ณผ๋ ToolCallingManager๋ก ๋ฐํ๋ฉ๋๋ค.
- ToolCallingManager๋ ๋๊ตฌ ์คํ ๊ฒฐ๊ณผ๋ฅผ ChatModel์ ๋ค์ ์ ๋ฌํฉ๋๋ค.
- ChatModel์ ๋๊ตฌ ์คํ ๊ฒฐ๊ณผ๋ฅผ ToolResponseMessage๋ก AI ๋ชจ๋ธ์ ์ ๋ฌํฉ๋๋ค.
- AI ๋ชจ๋ธ์ ๋๊ตฌ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ์ถ๊ฐ ๋ฌธ๋งฅ์ผ๋ก ์ฌ์ฉํ์ฌ ์ต์ข ์๋ต์ ์์ฑํ๊ณ , ์ด ์๋ต์ ChatClient๋ฅผ ํตํด ํธ์ถ์์๊ฒ ChatResponse๋ก ์ ๋ฌ๋ฉ๋๋ค.
์ฃผ์
ํ์ฌ ๋๊ตฌ ์คํ๊ณผ ๊ด๋ จํ์ฌ ๋ชจ๋ธ๊ณผ ์ฃผ๊ณ ๋ฐ๋ ๋ด๋ถ ๋ฉ์์ง๋ ์ฌ์ฉ์์๊ฒ ๋ ธ์ถ๋์ง ์์ต๋๋ค. ์ด๋ฌํ ๋ฉ์์ง์ ์ ๊ทผํ ํ์๊ฐ ์๋ค๋ฉด, ์ฌ์ฉ์ ์ ์ด ๋๊ตฌ ์คํ(user-controlled tool execution) ๋ฐฉ์์ ์ฌ์ฉํ์ ์ผ ํฉ๋๋ค.
๋๊ตฌ ํธ์ถ์ด ์คํ ๋์์ธ์ง ์ฌ๋ถ๋ฅผ ํ๋จํ๋ ๋ก์ง์ ToolExecutionEligibilityPredicate ์ธํฐํ์ด์ค์ ์ํด ์ฒ๋ฆฌ๋ฉ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก๋ ToolCallingChatOptions์ internalToolExecutionEnabled ์์ฑ์ด true(๊ธฐ๋ณธ๊ฐ)๋ก ์ค์ ๋์ด ์๊ณ , ChatResponse์ ๋๊ตฌ ํธ์ถ์ด ํฌํจ๋์ด ์๋์ง๋ฅผ ํ์ธํ์ฌ ์คํ ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํฉ๋๋ค.
public class DefaultToolExecutionEligibilityPredicate implements ToolExecutionEligibilityPredicate {
@Override
public boolean test(ChatOptions promptOptions, ChatResponse chatResponse) {
return ToolCallingChatOptions.isInternalToolExecutionEnabled(promptOptions) && chatResponse != null
&& chatResponse.hasToolCalls();
}
}
ChatModel ๋น์ ์์ฑํ ๋, ToolExecutionEligibilityPredicate์ ์ฌ์ฉ์ ์ ์ ๊ตฌํ์ ์ ๊ณตํ ์ ์์ต๋๋ค.
์ฌ์ฉ์ ์ ์ด ๋๊ตฌ ์คํ (User-Controlled Tool Execution)
๋๊ตฌ ์คํ ์๋ช ์ฃผ๊ธฐ๋ฅผ ์ง์ ์ ์ดํ๊ณ ์ถ์ ๊ฒฝ์ฐ๊ฐ ์์ ์ ์์ต๋๋ค. ์ด๋ด ๋๋ ToolCallingChatOption์ internalToolExecutionEnabled ์์ฑ์ false๋ก ์ค์ ํ๋ฉด ๋ฉ๋๋ค.
์ด ์ต์ ์ ์ค์ ํ ์ํ์์ ChatModel์ ํธ์ถํ๋ฉด, ๋๊ตฌ ์คํ์ ํธ์ถ์์๊ฒ ์์๋๋ฉฐ ์ ์ฒด ๋๊ตฌ ์คํ ํ๋ฆ์ ์ง์ ์ ์ดํ ์ ์๊ฒ ๋ฉ๋๋ค. ์ด ๊ฒฝ์ฐ, ChatResponse์ ๋๊ตฌ ํธ์ถ์ด ํฌํจ๋์ด ์๋์ง๋ฅผ ์ง์ ํ์ธํ๊ณ , ์ด๋ฅผ ToolCallingManager๋ฅผ ํตํด ์คํํ๋ ์ฑ ์์ ์ฌ์ฉ์์๊ฒ ์์ต๋๋ค.
๋ค์์ ์ฌ์ฉ์ ์ ์ด ๋ฐฉ์(user-controlled tool execution approach)์ ์ต์ ๊ตฌํ ์์์ ๋๋ค:
ChatModel chatModel = ...
ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(new CustomerTools())
.internalToolExecutionEnabled(false)
.build();
Prompt prompt = new Prompt("Tell me more about the customer with ID 42", chatOptions);
ChatResponse chatResponse = chatModel.call(prompt);
while (chatResponse.hasToolCalls()) {
ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(prompt, chatResponse);
prompt = new Prompt(toolExecutionResult.conversationHistory(), chatOptions);
chatResponse = chatModel.call(prompt);
}
System.out.println(chatResponse.getResult().getOutput().getText());
์ฐธ๊ณ
์ฌ์ฉ์ ์ ์ด ๋๊ตฌ ์คํ ๋ฐฉ์์ ์ ํํ ๊ฒฝ์ฐ, ๋๊ตฌ ์คํ ์์ ์ ๊ด๋ฆฌํ๊ธฐ ์ํด ToolCallingManager๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด Spring AI์์ ์ ๊ณตํ๋ ๋ด์ฅ ๋๊ตฌ ์คํ ๊ธฐ๋ฅ์ ๊ทธ๋๋ก ํ์ฉํ ์ ์์ต๋๋ค. ๋จ, ํ์ํ๋ค๋ฉด ๋๊ตฌ ์คํ ๋ก์ง์ ์ง์ ๊ตฌํํด ์ฌ์ฉํ๋ ๊ฒ๋ ๊ฐ๋ฅํฉ๋๋ค.
๋ค์ ์์๋ ChatMemory API๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ ์ฌ์ฉ์ ์ ์ด ๋๊ตฌ ์คํ ๋ฐฉ์(user-controlled tool execution approach)์ ์ต์ ๊ตฌํ ์๋ฅผ ๋ณด์ฌ์ค๋๋ค:
ToolCallingManager toolCallingManager = DefaultToolCallingManager.builder().build();
ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
String conversationId = UUID.randomUUID().toString();
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(ToolCallbacks.from(new MathTools()))
.internalToolExecutionEnabled(false)
.build();
Prompt prompt = new Prompt(
List.of(new SystemMessage("You are a helpful assistant."), new UserMessage("What is 6 * 8?")),
chatOptions);
chatMemory.add(conversationId, prompt.getInstructions());
Prompt promptWithMemory = new Prompt(chatMemory.get(conversationId), chatOptions);
ChatResponse chatResponse = chatModel.call(promptWithMemory);
chatMemory.add(conversationId, chatResponse.getResult().getOutput());
while (chatResponse.hasToolCalls()) {
ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(promptWithMemory,
chatResponse);
chatMemory.add(conversationId, toolExecutionResult.conversationHistory()
.get(toolExecutionResult.conversationHistory().size() - 1));
promptWithMemory = new Prompt(chatMemory.get(conversationId), chatOptions);
chatResponse = chatModel.call(promptWithMemory);
chatMemory.add(conversationId, chatResponse.getResult().getOutput());
}
UserMessage newUserMessage = new UserMessage("What did I ask you earlier?");
chatMemory.add(conversationId, newUserMessage);
ChatResponse newResponse = chatModel.call(new Prompt(chatMemory.get(conversationId)));
์์ธ ์ฒ๋ฆฌ
๋๊ตฌ ํธ์ถ์ด ์คํจํ๋ฉด ์์ธ๋ ToolExecutionException์ผ๋ก ์ ๋ฌ๋๋ฉฐ, ์ด๋ฅผ ์ก์์ ์ค๋ฅ๋ฅผ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ToolExecutionExceptionProcessor๋ฅผ ์ฌ์ฉํ๋ฉด ToolExecutionException์ ์ฒ๋ฆฌํ ์ ์์ผ๋ฉฐ, ์ฒ๋ฆฌ ๊ฒฐ๊ณผ๋ AI ๋ชจ๋ธ์ ์ ๋ฌํ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์์ฑํ๊ฑฐ๋, ํธ์ถ์๊ฐ ์ฒ๋ฆฌํ ์ ์๋๋ก ์์ธ๋ฅผ ๋ค์ ๋์ง๋ ๋ ๊ฐ์ง ๋ฐฉ์ ์ค ํ๋๊ฐ ๋ ์ ์์ต๋๋ค.
@FunctionalInterface
public interface ToolExecutionExceptionProcessor {
/**
* ๋๊ตฌ์์ ๋ฐ์ํ ์์ธ๋ฅผ AI ๋ชจ๋ธ์ ์ ๋ฌํ ์ ์๋ ๋ฌธ์์ด๋ก ๋ณํํ๊ฑฐ๋,
* ํธ์ถ์๊ฐ ์ฒ๋ฆฌํ ์ ์๋๋ก ์์ธ๋ฅผ ๋ค์ ๋์ง๋๋ค.
*/
String process(ToolExecutionException exception);
}
Spring AI Spring Boot Starter๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, ToolExecutionExceptionProcessor ์ธํฐํ์ด์ค์ ๊ธฐ๋ณธ ๊ตฌํ์ฒด๋ DefaultToolExecutionExceptionProcessor์ ๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก๋ ์ค๋ฅ ๋ฉ์์ง๊ฐ ๋ชจ๋ธ๋ก ์ ๋ฌ๋ฉ๋๋ค.
DefaultToolExecutionExceptionProcessor์ ์์ฑ์๋ฅผ ํตํด alwaysThrow ์์ฑ์ true ๋๋ false๋ก ์ค์ ํ ์ ์์ผ๋ฉฐ, true๋ก ์ค์ ํ๋ฉด ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋ชจ๋ธ์ ์ ๋ฌํ๋ ๋์ ์์ธ๊ฐ ํธ์ถ์์๊ฒ ๋ค์ ๋์ ธ์ง๋๋ค.
@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
return new DefaultToolExecutionExceptionProcessor(true);
}
์ฐธ๊ณ
์ฌ์ฉ์ ์ ์ ToolCallback ๊ตฌํ์ ์์ฑํ ๊ฒฝ์ฐ, call() ๋ฉ์๋ ๋ด์์ ๋๊ตฌ ์คํ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ๋ฐ๋์ ToolExecutionException์ ๋์ง๋๋ก ํด์ผ ํฉ๋๋ค.
ToolExecutionExceptionProcessor๋ ๋๊ตฌ ์คํ ์ค ๋ฐ์ํ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด ๊ธฐ๋ณธ ToolCallingManager (DefaultToolCallingManager) ๋ด๋ถ์์ ์ฌ์ฉ๋ฉ๋๋ค. ๋๊ตฌ ์คํ ์๋ช ์ฃผ๊ธฐ์ ๋ํ ์์ธํ ๋ด์ฉ์ Tool Execution ํญ๋ชฉ์ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.
๋๊ตฌ ํด์
๋ชจ๋ธ์ ๋๊ตฌ๋ฅผ ์ ๋ฌํ๋ ๊ธฐ๋ณธ ๋ฐฉ์์ ChatClient๋ ChatModel์ ํธ์ถํ ๋ ToolCallback(๋ค)์ ์ ๊ณตํ๋ ๊ฒ์ ๋๋ค. ์ด ๋ฐฉ์์ Methods as Tools ๋ฐ Functions as Tools์์ ์ค๋ช ํ ์ ๋ต์ ๋ฐ๋ฆ ๋๋ค.
๊ทธ๋ฌ๋ Spring AI๋ ToolCallbackResolver ์ธํฐํ์ด์ค๋ฅผ ํตํด ๋ฐํ์ ์ ๋๊ตฌ๋ฅผ ๋์ ์ผ๋ก ํด์ํ๋ ๊ธฐ๋ฅ๋ ์ง์ํฉ๋๋ค.
public interface ToolCallbackResolver {
/**
* ์ฃผ์ด์ง ๋๊ตฌ ์ด๋ฆ์ ํด๋นํ๋ {@link ToolCallback}์ ํด์ํ์ฌ ๋ฐํํฉ๋๋ค.
*/
@Nullable
ToolCallback resolve(String toolName);
}
์ด ๋ฐฉ์์ ์ฌ์ฉํ ๊ฒฝ์ฐ:
- ํด๋ผ์ด์ธํธ ์ธก์์๋, ToolCallback ์ธ์คํด์ค๋ฅผ ์ง์ ์ ๋ฌํ๋ ๋์ ๋๊ตฌ ์ด๋ฆ์ ChatClient ๋๋ ChatModel์ ์ ๊ณตํฉ๋๋ค.
- ์๋ฒ ์ธก์์๋, ToolCallbackResolver ๊ตฌํ์ฒด๊ฐ ๋๊ตฌ ์ด๋ฆ์ ์ค์ ToolCallback ์ธ์คํด์ค๋ก ํด์ํ๋ ์ญํ ์ ๋ด๋นํฉ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก Spring AI๋ ์ฌ๋ฌ ToolCallbackResolver ์ธ์คํด์ค์ ์์ํ๋ ๊ตฌ์กฐ์ธ DelegatingToolCallbackResolver๋ฅผ ์ฌ์ฉํฉ๋๋ค:
- SpringBeanToolCallbackResolver๋ Function, Supplier, Consumer, BiFunction ํ์ ์ Spring ๋น์์ ๋๊ตฌ๋ฅผ ํด์ํฉ๋๋ค. ์์ธํ ๋ด์ฉ์ Dynamic Specification: @Bean ํญ๋ชฉ์ ์ฐธ๊ณ ํ์ญ์์ค.
- StaticToolCallbackResolver๋ ์ ์ ์ธ ToolCallback ์ธ์คํด์ค ๋ชฉ๋ก์์ ๋๊ตฌ๋ฅผ ํด์ํฉ๋๋ค. Spring Boot ์๋ ๊ตฌ์ฑ(Spring Boot Autoconfiguration)์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, ์ ํ๋ฆฌ์ผ์ด์ ์ปจํ ์คํธ์ ์ ์๋ ๋ชจ๋ ToolCallback ๋น์ด ์๋์ผ๋ก ์ด ํด์๊ธฐ์ ๋ฑ๋ก๋ฉ๋๋ค.
Spring Boot ์๋ ๊ตฌ์ฑ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉ์ ์ ์ ToolCallbackResolver ๋น์ ์ ์ํ์ฌ ๋๊ตฌ ํด์ ๋ก์ง์ ์ปค์คํฐ๋ง์ด์ง ํ ์ ์์ต๋๋ค:
@Bean
ToolCallbackResolver toolCallbackResolver(List<FunctionCallback> toolCallbacks) {
StaticToolCallbackResolver staticToolCallbackResolver = new StaticToolCallbackResolver(toolCallbacks);
return new DelegatingToolCallbackResolver(List.of(staticToolCallbackResolver));
}
ToolCallbackResolver๋ ToolCallingManager ๋ด๋ถ์์ ์ฌ์ฉ๋์ด ๋ฐํ์์ ๋๊ตฌ๋ฅผ ๋์ ์ผ๋ก ํด์ํ๋ ์ญํ ์ ํ๋ฉฐ, ํ๋ ์์ํฌ ์ฃผ๋ ๋ฐฉ์(Framework-Controlled Tool Execution)๊ณผ ์ฌ์ฉ์ ์ฃผ๋ ๋ฐฉ์(User-Controlled Tool Execution) ๋ชจ๋๋ฅผ ์ง์ํฉ๋๋ค.
ํตํฉ๊ฐ์์ฑ (Observability)
๋๊ตฌ ํธ์ถ ๊ธฐ๋ฅ์ spring.ai.tool ๊ด์ธก ํญ๋ชฉ(observation)์ ํตํด ์๋ฃ ์๊ฐ ์ธก์ ๋ฐ ํธ๋ ์ด์ฑ ์ ๋ณด ์ ํ๋ฅผ ์ง์ํฉ๋๋ค. ์์ธํ ๋ด์ฉ์ Tool Calling Observability๋ฅผ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.
์ต์ ์ผ๋ก, Spring AI๋ ๋๊ตฌ ํธ์ถ์ ์ธ์ ๋ฐ ๊ฒฐ๊ณผ ๋ฐ์ดํฐ๋ฅผ ์คํฌ(span) ์์ฑ์ผ๋ก ๋ด๋ณด๋ผ ์ ์์ต๋๋ค. ์ด ๊ธฐ๋ฅ์ ๋ฏผ๊ฐํ ์ ๋ณด ๋ณดํธ๋ฅผ ์ํด ๊ธฐ๋ณธ์ ์ผ๋ก ๋นํ์ฑํ๋์ด ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์ Tool Call Arguments and Result Data ํญ๋ชฉ์ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.
๋ก๊น (Logging)
๋๊ตฌ ํธ์ถ ๊ธฐ๋ฅ์ ์ฃผ์ ๋์์ ๋ชจ๋ DEBUG ๋ ๋ฒจ๋ก ๋ก๊น ๋ฉ๋๋ค. ๊ด๋ จ ๋ก๊ทธ๋ฅผ ํ์ฑํํ๋ ค๋ฉด org.springframework.ai ํจํค์ง์ ๋ก๊ทธ ๋ ๋ฒจ์ DEBUG๋ก ์ค์ ํ์๋ฉด ๋ฉ๋๋ค.
Reference
'๐ ๊ณต์ ๋ฌธ์ ๋ฒ์ญ > Spring AI (2025 Renewal)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Spring AI 2025 Renewal #8] Model Context Protocol (MCP) (0) | 2025.06.03 |
---|---|
[Spring AI 2025 Renewal #6] Chat Memory (0) | 2025.05.17 |
[Spring AI 2025 Renewal #5] Embedding Models & Audio Models & Moderation Models (0) | 2025.05.17 |
[Spring AI 2025 Renewal #4] Chat Models & Image Models (1) | 2025.04.12 |
[Spring AI 2025 Renewal #3] Advisors API (0) | 2025.04.06 |