์๋ ํ์ธ์ dev_writer์ ๋๋ค.
์ด๋ฒ ์๊ฐ์๋ Spring AI ๊ณต์ ๋ฌธ์ ์ค Advisors API์ ๋ํด ๋ฒ์ญํ ๋ด์ฉ์ ์ ๋ฌํด ๋๋ฆฌ๊ฒ ์ต๋๋ค.
Advisors API
Spring AI์ Advisors API๋ Spring AI ์ ํ๋ฆฌ์ผ์ด์ ๋ด์์ AI ๊ธฐ๋ฐ ์ํธ์์ฉ์ ๊ฐ๋ก์ฑ๊ณ , ์์ ํ๋ฉฐ, ํ์ฅํ ์ ์๋ ์ ์ฐํ๊ณ ๊ฐ๋ ฅํ ์๋จ์ ์ ๊ณตํฉ๋๋ค.
Advisors API๋ฅผ ํ์ฉํ๋ฉด ๋ ์ ๊ตํ๊ณ , ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๋ฉฐ, ์ ์ง๋ณด์๊ฐ ์ฉ์ดํ AI ์ปดํฌ๋ํธ๋ฅผ ๊ตฌํํ ์ ์์ต๋๋ค.
์ฃผ์ ์ด์ ์ผ๋ก๋ ๋ฐ๋ณต์ ์ธ ์์ฑํ AI ํจํด์ ์บก์ํํ๊ณ , ๋ํ ์ธ์ด ๋ชจ๋ธ (LLM)๋ก ์ก์์ ๋๋ ๋ฐ์ดํฐ๋ฅผ ๋ณํํ๋ฉฐ, ๋ค์ํ ๋ชจ๋ธ ๋ฐ ์ฌ์ฉ ์ฌ๋ก ์ ๋ฐ์ ๊ฑธ์ณ ์ด์์ฑ์ ์ ๊ณตํ๋ ๊ฒ์ด ํฌํจ๋ฉ๋๋ค.
๋ค์ ์์์ ๊ฐ์ด ChatClient API๋ฅผ ์ฌ์ฉํ์ฌ ๊ธฐ์กด advisor๋ค์ ๊ตฌ์ฑํ ์ ์์ต๋๋ค:
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory), // chat-memory advisor
new QuestionAnswerAdvisor(vectorStore) // RAG advisor
)
.build();
String response = this.chatClient.prompt()
// Set advisor parameters at runtime
.advisors(advisor -> advisor.param("chat_memory_conversation_id", "678")
.param("chat_memory_response_size", 100))
.user(userText)
.call()
.content();
advisor๋ builder์ defaultAdvisors() ๋ฉ์๋๋ฅผ ์ฌ์ฉํด ๋น๋ ํ์์ ๋ฑ๋กํ๋ ๊ฒ์ด ๊ถ์ฅ๋ฉ๋๋ค.
advisor๋ ๊ด์ฐฐ ๊ฐ๋ฅ์ฑ (Observability) ์คํ์๋ ์ฐธ์ฌํ๋ฏ๋ก, ์คํ๊ณผ ๊ด๋ จ๋ ๋ฉํธ๋ฆญ ๋ฐ ํธ๋ ์ด์ค๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
Question Answer Advisor์ ๋ํด ๋ ์์๋ณด๊ธฐ
ํต์ฌ ์ปดํฌ๋ํธ
์ด API๋ ์คํธ๋ฆฌ๋ฐ์ด ์๋ ์๋๋ฆฌ์ค์ ๋ํด CallAroundAdvisor ๋ฐ CallAroundAdvisorChain์ ํฌํจํ๊ณ , ์คํธ๋ฆฌ๋ฐ ์๋๋ฆฌ์ค์ ๋ํด์๋ StreamAroundAdvisor ๋ฐ StreamAroundAdvisorChain์ ํฌํจํฉ๋๋ค. ๋ํ AdvisedRequest๋ ํ์ ๋์ง ์์ Prompt ์์ฒญ์ ๋ํ๋ด๋ฉฐ, AdvisedResponse๋ Chat Completion ์๋ต์ ๋ํ๋ ๋๋ค. ๋ ๋ค advisor ์ฒด์ธ ์ ๋ฐ์์ ์ํ๋ฅผ ๊ณต์ ํ ์ ์๋ advise-context๋ฅผ ํฌํจํฉ๋๋ค.
nextAroundCall() ๋ฐ nextAroundStream()๋ ์ฃผ์ advisor ๋ฉ์๋๋ก, ์ผ๋ฐ์ ์ผ๋ก ํ์ ๋์ง ์์ Prompt ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฌํ๊ณ , Prompt ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉ์ ์ ์ํ๊ฑฐ๋ ๋ณด๊ฐํ๋ฉฐ, advisor ์ฒด์ธ์์ ๋ค์ ์ํฐํฐ๋ฅผ ํธ์ถํ๊ณ , ์ ํ์ ์ผ๋ก ์์ฒญ์ ์ฐจ๋จํ๋ฉฐ, chat completion ์๋ต์ ๊ฒ์ฌํ๊ณ , ์ฒ๋ฆฌ ์ค๋ฅ๋ฅผ ๋ํ๋ด๊ธฐ ์ํด ์์ธ๋ฅผ ๋ฐ์์ํค๋ ๋ฑ์ ๋์์ ์ํํฉ๋๋ค.
์ถ๊ฐ๋ก, getOrder() ๋ฉ์๋๋ ์ฒด์ธ์์ advisor์ ์์๋ฅผ ๊ฒฐ์ ํ๊ณ , getName()์ ๊ณ ์ ํ advisor ์ด๋ฆ์ ์ ๊ณตํฉ๋๋ค.
Spring AI ํ๋ ์์ํฌ์ ์ํด ์์ฑ๋ Advisor Chain์ getOrder() ๊ฐ์ ๋ฐ๋ผ ์ ๋ ฌ๋ ์ฌ๋ฌ advisor๋ค์ ์์ฐจ์ ์ผ๋ก ํธ์ถํ ์ ์๋๋ก ํฉ๋๋ค. ๋ฎ์ ๊ฐ์ด ๋จผ์ ์คํ๋ฉ๋๋ค. ๋ง์ง๋ง advisor๋ ์๋์ผ๋ก ์ถ๊ฐ๋๋ฉฐ, LLM์ ์์ฒญ์ ์ ์กํฉ๋๋ค.
๋ค์์ ํ๋ก์ฐ ๋ค์ด์ด๊ทธ๋จ์ advisor ์ฒด์ธ๊ณผ Chat Model ๊ฐ์ ์ํธ์์ฉ์ ๋ณด์ฌ์ค๋๋ค.
- Spring AI ํ๋ ์์ํฌ๋ ์ฌ์ฉ์์ Prompt๋ก๋ถํฐ AdvisedRequest๋ฅผ ์์ฑํ๊ณ , ๋น์ด ์๋ AdvisorContext ๊ฐ์ฒด๋ฅผ ํจ๊ป ์์ฑํฉ๋๋ค.
- ์ฒด์ธ์ ๊ฐ advisor๋ ํด๋น ์์ฒญ์ ์ฒ๋ฆฌํ๋ฉฐ, ํ์์ ๋ฐ๋ผ ์์ฒญ์ ์์ ํ ์ ์์ต๋๋ค. ๋๋ ๋ค์ ์ํฐํฐ ํธ์ถ์ ์๋ตํ์ฌ ์์ฒญ์ ์ฐจ๋จํ ์๋ ์์ต๋๋ค. ํ์์ ๊ฒฝ์ฐ, ํด๋น advisor๋ ์๋ต์ ์ง์ ์์ฑํ ์ฑ ์์ด ์์ต๋๋ค.
- ํ๋ ์์ํฌ์์ ์ ๊ณตํ๋ ์ต์ข advisor๋ ์์ฒญ์ Chat Model์ ์ ์กํฉ๋๋ค.
- Chat Model์ ์๋ต์ advisor ์ฒด์ธ์ ๊ฑฐ์ณ ๋ค์ ์ ๋ฌ๋๋ฉฐ, AdvisedResponse๋ก ๋ณํ๋ฉ๋๋ค. ์ด ์๋ต์๋ ๊ณต์ ๋ AdvisorContext ์ธ์คํด์ค๊ฐ ํฌํจ๋ฉ๋๋ค.
- ๊ฐ advisor๋ ์๋ต์ ์ฒ๋ฆฌํ๊ฑฐ๋ ์์ ํ ์ ์์ต๋๋ค.
- ์ต์ข AdvisedResponse๋ ChatCompletion์ ์ถ์ถํ์ฌ ํด๋ผ์ด์ธํธ์ ๋ฐํ๋ฉ๋๋ค.
Advisor ์์
advisor ์ฒด์ธ์์์ ์คํ ์์๋ getOrder() ๋ฉ์๋์ ์ํด ๊ฒฐ์ ๋ฉ๋๋ค. ๋ค์์ ์ดํดํด์ผ ํ ์ฃผ์ ์ฌํญ์ ๋๋ค.
- order ๊ฐ์ด ๋ฎ์์๋ก advisor๋ ๋จผ์ ์คํ๋ฉ๋๋ค.
- advisor ์ฒด์ธ์ ์คํ์ฒ๋ผ ๋์ํฉ๋๋ค.
- ์ฒด์ธ์ ์ฒซ ๋ฒ์งธ advisor๋ ์์ฒญ์ ๊ฐ์ฅ ๋จผ์ ์ฒ๋ฆฌํฉ๋๋ค.
- ๊ทธ๋ฆฌ๊ณ ์๋ต์ ๊ฐ์ฅ ๋ง์ง๋ง์ ์ฒ๋ฆฌํฉ๋๋ค.
- ์คํ ์์๋ฅผ ์ ์ดํ๋ ค๋ฉด:
- Ordered.HIGHEST_PRECEDENCE์ ๊ฐ๊น์ด ๊ฐ์ ์ค์ ํ๋ฉด advisor๋ ์ฒด์ธ์์ ๊ฐ์ฅ ๋จผ์ ์์ฒญ์ ์ฒ๋ฆฌํ๊ณ , ์๋ต์ ๊ฐ์ฅ ๋ง์ง๋ง์ ์ฒ๋ฆฌํฉ๋๋ค.
- Ordered.LOWEST_PRECEDENCE์ ๊ฐ๊น์ด ๊ฐ์ ์ค์ ํ๋ฉด advisor๋ ์ฒด์ธ์์ ๊ฐ์ฅ ๋ง์ง๋ง์ ์์ฒญ์ ์ฒ๋ฆฌํ๊ณ , ์๋ต์ ๊ฐ์ฅ ๋จผ์ ์ฒ๋ฆฌํฉ๋๋ค.
- order ๊ฐ์ด ๋์์๋ก ์ฐ์ ์์๋ ๋ฎ๊ฒ ํด์๋ฉ๋๋ค.
- ์ฌ๋ฌ advisor๊ฐ ๊ฐ์ order ๊ฐ์ ๊ฐ์ง ๊ฒฝ์ฐ, ์คํ ์์๋ ๋ณด์ฅ๋์ง ์์ต๋๋ค.
์ฐธ๊ณ
order ๊ฐ๊ณผ ์คํ ์์ ์ฌ์ด์ ํผ๋์ด ์์ ์ ์๋ ์ด์ ๋, advisor ์ฒด์ธ์ด ์คํ์ฒ๋ผ ๋์ํ๊ธฐ ๋๋ฌธ์ ๋๋ค: ์ฐ์ ์์๊ฐ ๊ฐ์ฅ ๋์ advisor (๊ฐ์ฅ ๋ฎ์ order ๊ฐ)๋ ์คํ์ ๊ฐ์ฅ ์์ ์ถ๊ฐ๋ฉ๋๋ค. ์์ฒญ ์ฒ๋ฆฌ ์์๋ ์คํ์ด ํ๋ฆฌ๋ฉด์ ๊ฐ์ฅ ๋จผ์ ์คํ๋ฉ๋๋ค. ์๋ต ์ฒ๋ฆฌ ์์๋ ์คํ์ด ๋ค์ ์์ด๋ฉด์ ๊ฐ์ฅ ๋ง์ง๋ง์ ์คํ๋ฉ๋๋ค.
๋ค์์ Spring์ Ordered ์ธํฐํ์ด์ค๊ฐ ์๋ฏธํ๋ ๋ฐ์ ๋๋ค.
public interface Ordered {
/**
* ๊ฐ์ฅ ๋์ ์ฐ์ ์์๋ฅผ ๋ํ๋ด๋ ์์์
๋๋ค.
* @see java.lang.Integer#MIN_VALUE
*/
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
/**
* ๊ฐ์ฅ ๋ฎ์ ์ฐ์ ์์๋ฅผ ๋ํ๋ด๋ ์์์
๋๋ค.
* @see java.lang.Integer#MAX_VALUE
*/
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
/**
* ์ด ๊ฐ์ฒด์ order ๊ฐ์ ๋ฐํํฉ๋๋ค.
* <p>๊ฐ์ด ๋์์๋ก ์ฐ์ ์์๋ ๋ฎ๊ฒ ํด์๋ฉ๋๋ค. ๊ทธ ๊ฒฐ๊ณผ,
* ๊ฐ์ฅ ๋ฎ์ ๊ฐ์ ๊ฐ์ง ๊ฐ์ฒด๊ฐ ๊ฐ์ฅ ๋์ ์ฐ์ ์์๋ฅผ ๊ฐ์ง๊ฒ ๋ฉ๋๋ค
* (์๋ธ๋ฆฟ์ {@code load-on-startup} ๊ฐ๊ณผ ์ ์ฌํ ๊ฐ๋
์
๋๋ค).
* <p>๋์ผํ order ๊ฐ์ ๊ฐ์ง๋ ๊ฒฝ์ฐ, ํด๋น ๊ฐ์ฒด๋ค์ ์ ๋ ฌ ์์น๋ ์์๋ก ๊ฒฐ์ ๋ฉ๋๋ค.
* @return order ๊ฐ
* @see #HIGHEST_PRECEDENCE
* @see #LOWEST_PRECEDENCE
*/
int getOrder();
}
ํ
์ ๋ ฅ๊ณผ ์ถ๋ ฅ ์์ชฝ ๋ชจ๋์์ ์ฒด์ธ์ ์ฒซ ๋ฒ์งธ์ ์์นํด์ผ ํ๋ ์ฌ์ฉ ์ฌ๋ก์ ๊ฒฝ์ฐ:
- ๊ฐ๊ฐ์ ์ธก๋ฉด์ ๋ํด ๋ณ๋์ advisor๋ฅผ ์ฌ์ฉํ์ญ์์ค.
- ์ด๋ค advisor์ ์๋ก ๋ค๋ฅธ order ๊ฐ์ ์ค์ ํ์ญ์์ค.
- advisor context๋ฅผ ์ฌ์ฉํ์ฌ ๋ advisor ๊ฐ์ ์ํ๋ฅผ ๊ณต์ ํ์ญ์์ค.
API ๊ฐ์
์ฃผ์ Advisor ์ธํฐํ์ด์ค๋ค์ org.springframework.ai.chat.client.advisor.api ํจํค์ง์ ์์นํด ์์ต๋๋ค. ์์ ๋ง์ advisor๋ฅผ ์์ฑํ ๋ ๋ง์ฃผํ๊ฒ ๋ ํต์ฌ ์ธํฐํ์ด์ค๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
public interface Advisor extends Ordered {
String getName();
}
๋๊ธฐ ๋ฐ ๋ฆฌ์กํฐ๋ธ ๋ฐฉ์์ Advisor๋ฅผ ์ํ ๋ ๊ฐ์ ํ์ ์ธํฐํ์ด์ค๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
public interface CallAroundAdvisor extends Advisor {
/**
* ChatModel#call(Prompt) ๋ฉ์๋๋ฅผ ๊ฐ์ธ๋ around advice์
๋๋ค.
* @param advisedRequest advisor๊ฐ ์ ์ฉ๋ ์์ฒญ ๊ฐ์ฒด
* @param chain advisor ์ฒด์ธ
* @return ์๋ต
*/
AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain);
}
๊ทธ๋ฆฌ๊ณ
public interface StreamAroundAdvisor extends Advisor {
/**
* advisor๊ฐ ์ ์ฉ๋ ์์ฒญ ํธ์ถ์ ๊ฐ์ธ๋ around advice์
๋๋ค.
* @param advisedRequest advisor๊ฐ ์ ์ฉ๋ ์์ฒญ ๊ฐ์ฒด
* @param chain ์คํ๋ advisor ์ฒด์ธ
* @return advisor๊ฐ ์ ์ฉ๋ ์์ฒญ์ ๊ฒฐ๊ณผ
*/
Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain);
}
Advice์ ์ฒด์ธ์ ๊ณ์ ์งํํ๋ ค๋ฉด, ์์ ์ Advice ๊ตฌํ ๋ด์์ CallAroundAdvisorChain๊ณผ StreamAroundAdvisorChain์ ์ฌ์ฉํ์ญ์์ค.
// ๋๊ธฐ ๋ฐฉ์
AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
// ์ฒด์ธ์ ๋ค์ advisor๋ฅผ ํธ์ถํ์ฌ ์ฒด์ธ ๊ณ์ ์งํ
return chain.next(advisedRequest);
}
// ๋ฆฌ์กํฐ๋ธ (์คํธ๋ฆฌ๋ฐ) ๋ฐฉ์
Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
// ์ฒด์ธ์ ๋ค์ advisor๋ฅผ ํธ์ถํ์ฌ ์ฒด์ธ ๊ณ์ ์งํ
return chain.next(advisedRequest);
}
Advisor ๊ตฌํ
advisor๋ฅผ ์์ฑํ๋ ค๋ฉด CallAroundAdvisor ๋๋ StreamAroundAdvisor (๋๋ ๋ ๋ค)๋ฅผ ๊ตฌํํ์ญ์์ค.
๊ตฌํํด์ผ ํ ํต์ฌ ๋ฉ์๋๋ ๋น ์คํธ๋ฆฌ๋ฐ advisor์ ๊ฒฝ์ฐ nextAroundCall(), ์คํธ๋ฆฌ๋ฐ advisor์ ๊ฒฝ์ฐ nextAroundStream()์ ๋๋ค.
์์
์ฐ๋ฆฌ๋ ๊ด์ฐฐ (Observing) ๋ฐ ๋ณด๊ฐ (Augmenting) ์ฌ๋ก๋ฅผ ์ํ advisor๋ฅผ ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ๋ณด์ฌ์ฃผ๊ธฐ ์ํด ๋ช ๊ฐ์ง ์ค์ต ์์ ๋ฅผ ์ ๊ณตํ ์์ ์ ๋๋ค.
๋ก๊น Advisor
์ฐ๋ฆฌ๋ ๋ค์ advisor๋ฅผ ์ฒด์ธ์์ ํธ์ถํ๊ธฐ ์ ๊ณผ ํ์ AdvisedRequest์ AdvisedResponse๋ฅผ ๋ก๊น ํ๋ ๊ฐ๋จํ ๋ก๊น advisor๋ฅผ ๊ตฌํํ ์ ์์ต๋๋ค.
์ด advisor๋ ์์ฒญ ๋ฐ ์๋ต์ ๊ด์ฐฐ๋ง ํ ๋ฟ ์์ ํ์ง ์์ผ๋ฉฐ, ๋น ์คํธ๋ฆฌ๋ฐ๊ณผ ์คํธ๋ฆฌ๋ฐ ์๋๋ฆฌ์ค ๋ชจ๋๋ฅผ ์ง์ํฉ๋๋ค.
public class SimpleLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
private static final Logger logger = LoggerFactory.getLogger(SimpleLoggerAdvisor.class);
@Override
public String getName() { // 1๏ธโฃ
return this.getClass().getSimpleName();
}
@Override
public int getOrder() { // 2๏ธโฃ
return 0;
}
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
logger.debug("BEFORE: {}", advisedRequest);
AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);
logger.debug("AFTER: {}", advisedResponse);
return advisedResponse;
}
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
logger.debug("BEFORE: {}", advisedRequest);
Flux<AdvisedResponse> advisedResponses = chain.nextAroundStream(advisedRequest);
return new MessageAggregator().aggregateAdvisedResponse(advisedResponses,
advisedResponse -> logger.debug("AFTER: {}", advisedResponse)); // 3๏ธโฃ
}
}
- advisor์ ๊ณ ์ ํ ์ด๋ฆ์ ์ ๊ณตํ๋ ค๋ฉด getName() ๋ฉ์๋๋ฅผ ๊ตฌํํฉ๋๋ค.
- getOrder() ๊ฐ์ ์ค์ ํ์ฌ ์คํ ์์๋ฅผ ์ ์ดํ ์ ์์ต๋๋ค. ๊ฐ์ด ๋ฎ์์๋ก ๋จผ์ ์คํ๋ฉ๋๋ค.
- MessageAggregator๋ Flux ์๋ต์ ํ๋์ AdvisedResponse๋ก ์ง๊ณํ๋ ์ ํธ๋ฆฌํฐ ํด๋์ค์ ๋๋ค.
์ด๋ ์คํธ๋ฆผ ๋ด ๊ฐ๋ณ ํญ๋ชฉ์ด ์๋ ์ ์ฒด ์๋ต์ ๊ด์ฐฐํ๊ฑฐ๋ ๋ก๊น ๋ฑ์ ์์ ์ ์ํํ ๋ ์ ์ฉํฉ๋๋ค.
๋จ, MessageAggregator๋ ์ฝ๊ธฐ ์ ์ฉ (read-only) ์์ ์ด๋ฏ๋ก ์๋ต์ ์์ ํ ์๋ ์์ต๋๋ค.
Re-Reading (Re2) Advisor
"Re-Reading Improves Reasoning in Large Language Models"๋ผ๋ ๋ ผ๋ฌธ์์๋ LLM์ ์ถ๋ก ๋ฅ๋ ฅ์ ํฅ์ํ๋ Re-Reading (Re2) ๊ธฐ๋ฒ์ ์๊ฐํฉ๋๋ค.
์ด ๊ธฐ๋ฒ์ ์ ๋ ฅ ํ๋กฌํํธ๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ๋ณด๊ฐํฉ๋๋ค.
{Input_Query}
Read the question again: {Input_Query}
์ฌ์ฉ์์ ์ ๋ ฅ ์ฟผ๋ฆฌ์ Re2 ๊ธฐ๋ฒ์ ์ ์ฉํ๋ advisor๋ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌํํ ์ ์์ต๋๋ค.
public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
private AdvisedRequest before(AdvisedRequest advisedRequest) { // 1๏ธโฃ
Map<String, Object> advisedUserParams = new HashMap<>(advisedRequest.userParams());
advisedUserParams.put("re2_input_query", advisedRequest.userText());
return AdvisedRequest.from(advisedRequest)
.userText("""
{re2_input_query}
Read the question again: {re2_input_query}
""")
.userParams(advisedUserParams)
.build();
}
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { // 2๏ธโฃ
return chain.nextAroundCall(this.before(advisedRequest));
}
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { // 3๏ธโฃ
return chain.nextAroundStream(this.before(advisedRequest));
}
@Override
public int getOrder() { // 4๏ธโฃ
return 0;
}
@Override
public String getName() { // 5๏ธโฃ
return this.getClass().getSimpleName();
}
}
- before ๋ฉ์๋๋ Re-Reading ๊ธฐ๋ฒ์ ์ ์ฉํ์ฌ ์ฌ์ฉ์์ ์ ๋ ฅ ์ฟผ๋ฆฌ๋ฅผ ๋ณด๊ฐํฉ๋๋ค.
- aroundCall ๋ฉ์๋๋ ๋น ์คํธ๋ฆฌ๋ฐ ์์ฒญ์ ๊ฐ๋ก์ฑ๊ณ Re2 ๊ธฐ๋ฒ์ ์ ์ฉํฉ๋๋ค.
- aroundStream ๋ฉ์๋๋ ์คํธ๋ฆฌ๋ฐ ์์ฒญ์ ๊ฐ๋ก์ฑ๊ณ Re2 ๊ธฐ๋ฒ์ ์ ์ฉํฉ๋๋ค.
- getOrder()๋ฅผ ํตํด ์คํ ์์๋ฅผ ์ ์ดํ ์ ์์ต๋๋ค. ๊ฐ์ด ๋ฎ์์๋ก ๋จผ์ ์คํ๋ฉ๋๋ค.
- getName()์ advisor์ ๊ณ ์ ์ด๋ฆ์ ์ ๊ณตํฉ๋๋ค.
Spring AI ๋ด์ฅ Advisor
Spring AI ํ๋ ์์ํฌ๋ AI ์ํธ์์ฉ์ ํฅ์ํ๊ธฐ ์ํ ์ฌ๋ฌ ๋ด์ฅ Advisor๋ฅผ ์ ๊ณตํฉ๋๋ค. ์๋๋ ์ฌ์ฉ ๊ฐ๋ฅํ Advisor๋ค์ ๊ฐ์์ ๋๋ค.
- Chat Memory Advisors: ์ด๋ฌํ Advisor๋ค์ ๋ํ ์ด๋ ฅ์ chat memory ์ ์ฅ์์์ ๊ด๋ฆฌํฉ๋๋ค.
- MessageChatMemoryAdvisor: ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์กฐํํ์ฌ ๋ฉ์์ง ์ปฌ๋ ์ ํํ๋ก ํ๋กฌํํธ์ ์ถ๊ฐํฉ๋๋ค. ์ด ๋ฐฉ์์ ๋ํ ์ด๋ ฅ์ ๊ตฌ์กฐ๋ฅผ ์ ์งํ ์ ์์ง๋ง, ๋ชจ๋ AI ๋ชจ๋ธ์ด ์ด ํ์์ ์ง์ํ์ง๋ ์์ต๋๋ค.
- PromptChatMemoryAdvisor: ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์กฐํํ์ฌ ํ๋กฌํํธ์ ์์คํ ํ ์คํธ์ ์ฝ์ ํฉ๋๋ค.
- VectorStoreChatMemoryAdvisor: VectorStore์์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์กฐํํ๊ณ , ์ด๋ฅผ ํ๋กฌํํธ์ ์์คํ ํ ์คํธ์ ์ฝ์ ํฉ๋๋ค. ๋๊ท๋ชจ ๋ฐ์ดํฐ์ ์์ ๊ด๋ จ ์ ๋ณด๋ฅผ ํจ์จ์ ์ผ๋ก ๊ฒ์ ๋ฐ ์ฝ์ ํ ์ ์์ด ์ ์ฉํฉ๋๋ค.
- Question Answering Advisor
- QuestionAnswerAdvisor: VectorStore๋ฅผ ํ์ฉํ์ฌ ์ง๋ฌธ-์๋ต ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. ์ด๋ RAG (Retrieval-Augmented Generation, ๊ฒ์ ์ฆ๊ฐ ์์ฑ) ํจํด์ ๊ตฌํํฉ๋๋ค.
- Content Safety Advisor
- SafeGuardAdvisor: ๋ชจ๋ธ์ด ์ ํดํ๊ฑฐ๋ ๋ถ์ ์ ํ ์ฝํ ์ธ ๋ฅผ ์์ฑํ์ง ์๋๋ก ๋ฐฉ์งํ๋ ๊ฐ๋จํ Advisor์ ๋๋ค.
์คํธ๋ฆฌ๋ฐ vs ๋น ์คํธ๋ฆฌ๋ฐ
๋น ์คํธ๋ฆฌ๋ฐ (Non-streaming) advisor๋ ์์ ํ ์์ฒญ ๋ฐ ์๋ต ๊ฐ์ฒด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์๋ํฉ๋๋ค.
์คํธ๋ฆฌ๋ฐ (Streaming) advisor๋ ์์ฒญ ๋ฐ ์๋ต์ ์ฐ์์ ์ธ ์คํธ๋ฆผ์ผ๋ก ์ฒ๋ฆฌํ๋ฉฐ, ๋ฐ์ํ ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋ (์: ์๋ต์ Flux ์ฌ์ฉ)์ ๊ธฐ๋ฐ์ผ๋ก ํฉ๋๋ค.
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
return Mono.just(advisedRequest)
.publishOn(Schedulers.boundedElastic())
.map(request -> {
// ์ด ์ฝ๋๋ ๋ธ๋กํน ๋๋ ๋
ผ๋ธ๋กํน ์ค๋ ๋์์ ์คํ๋ ์ ์์ต๋๋ค.
// next ํธ์ถ ์ advisor ๋ก์ง ์คํ
return request;
})
.flatMapMany(request -> chain.nextAroundStream(request))
.map(response -> {
// next ํธ์ถ ํ advisor ๋ก์ง ์คํ
return response;
});
}
๋ชจ๋ฒ ์ฌ๋ก
- Advisor๋ ํน์ ์์ ์ ์ง์คํ์ฌ ๊ตฌํํจ์ผ๋ก์จ ๋ชจ๋ํ๋ฅผ ๋์ด์ญ์์ค.
- ํ์ํ ๊ฒฝ์ฐ advisor ๊ฐ ์ํ ๊ณต์ ๋ฅผ ์ํด adviseContext๋ฅผ ํ์ฉํ์ญ์์ค.
- ์คํธ๋ฆฌ๋ฐ๊ณผ ๋น ์คํธ๋ฆฌ๋ฐ ๋ฒ์ ๋ชจ๋๋ฅผ ๊ตฌํํ์ฌ ์ต๋ํ์ ์ ์ฐ์ฑ์ ํ๋ณดํ์ญ์์ค.
- ๋ฐ์ดํฐ ํ๋ฆ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ด๋ฃจ์ด์ง๋๋ก advisor์ ์คํ ์์๋ฅผ ์ ์คํ๊ฒ ๊ณ ๋ คํ์ญ์์ค.
ํ์ ํธํ์ฑ
์ค์
AdvisedRequest๊ฐ ์ ํจํค์ง๋ก ์ด๋๋์์ต๋๋ค.
์ค๋ํ API ๋ณ๊ฒฝ ์ฌํญ
Advisor ์ธํฐํ์ด์ค
- 1.0 M2 ๋ฒ์ ์์๋ RequestAdvisor์ ResponseAdvisor๋ผ๋ ๋ณ๋์ ์ธํฐํ์ด์ค๊ฐ ์์์ต๋๋ค.
- RequestAdvisor๋ ChatModel.call ๋ฐ ChatModel.stream ๋ฉ์๋๊ฐ ํธ์ถ๋๊ธฐ ์ด์ ์ ์คํ๋์์ต๋๋ค.
- ResponseAdvisor๋ ํด๋น ๋ฉ์๋๊ฐ ํธ์ถ๋ ์ดํ์ ์คํ๋์์ต๋๋ค.
- 1.0 M3 ๋ฒ์ ์์๋ ์ ์ธํฐํ์ด์ค๋ค์ด ๋ค์์ผ๋ก ๋์ฒด๋์์ต๋๋ค.
- CallAroundAdvisor
- StreamAroundAdvisor
- ์ด์ ์ ResponseAdvisor์ ์ผ๋ถ์๋ StreamResponseMode๋ ์ ๊ฑฐ๋์์ต๋๋ค.
Context Map ์ฒ๋ฆฌ
- 1.0 M2 ๋ฒ์ ์์๋
- context map์ด ๋ณ๋์ ๋ฉ์๋ ์ธ์๋ก ์ ๋ฌ๋์์ต๋๋ค.
- ์ด map์ ๋ณ๊ฒฝ ๊ฐ๋ฅ (mutable) ํ๋ฉฐ ์ฒด์ธ์ ๋ฐ๋ผ ์ ๋ฌ๋์์ต๋๋ค.
- 1.0 M3 ๋ฒ์ ์์๋
- context map์ด ์ด์ AdvisedRequest์ AdvisedResponse ๋ ์ฝ๋์ ์ผ๋ถ๋ก ํตํฉ๋์์ต๋๋ค.
- map์ ๋ถ๋ณ (immutable)์ ๋๋ค.
- context๋ฅผ ์ ๋ฐ์ดํธํ๋ ค๋ฉด updateContext ๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ผ ํ๋ฉฐ, ์ด ๋ฉ์๋๋ ์ ๋ฐ์ดํธ๋ ๋ด์ฉ์ ๊ฐ์ง ์๋ก์ด ๋ณ๊ฒฝ ๋ถ๊ฐ๋ฅํ map์ ์์ฑํฉ๋๋ค.
1.0 M3์์ context๋ฅผ ์ ๋ฐ์ดํธํ๋ ์์์ ๋๋ค.
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
this.advisedRequest = advisedRequest.updateContext(context -> {
context.put("aroundCallBefore" + getName(), "AROUND_CALL_BEFORE " + getName()); // ์ฌ๋ฌ key-value ์ถ๊ฐ
context.put("lastBefore", getName()); // ๋จ์ผ key-value ์ถ๊ฐ
return context;
});
// ๋ฉ์๋ ๊ตฌํ ๊ณ์...
}
์ค์ ์ ์ฉํด๋ณด๊ธฐ
์ฌ์ ์ค๋น
๋จผ์ , start.spring.io ์์ Spring Web๊ณผ OpenAI ๊ตฌํ์ฒด๋ฅผ ์์กด๋ฐ๋๋ก ์ค์ ํด๋ก๋๋ค.
์ดํ, application.properties์ ๋ค์ ๊ฐ๋ค์ ์ธํ ํด๋ก๋๋ค.
spring.application.name=demo
spring.ai.openai.chat.options.model={AI ๋ชจ๋ธ (ex: gpt-4o)}
spring.ai.openai.api-key={API ํค}
logging.level.root=DEBUG // SimpleLoggerAdvisor๋ฅผ ์ด์ฉํ ๋ ํ์
์ฝ๋ ์์ฑ: MessageChatMemoryAdvisor ์ ์ฉ ์ด์
MessageChatMemoryAdvisor๋ฅผ ์ ์ฉํ๊ธฐ ์ด์ ์ ์ฝ๋๋ฅผ ์์ฑํด๋ณด๋ฉด ์๋์ ๊ฐ์ต๋๋ค. ๋ฉ์์ง๋ฅผ ๋ฐ์ผ๋ฉด ๋ฐ๋ก ์๋ต์ ์ด๋์ด๋ด๋ home ๋ฉ์๋๋ฅผ ์์ฑํ์ต๋๋ค.
package com.example.demo;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@GetMapping("/")
public String home(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}
์ดํ ์ค์ ๋ก ์คํํด๋ณด๋ฉด, ์ด๋ฆ์ ๊ธฐ์ตํ์ง ๋ชป ํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค. (ํ ์คํธ๋ httpie๋ก ์งํํ์ต๋๋ค.)
์ฝ๋ ์์ฑ: MessageChatMemoryAdvisor ์ ์ฉ ์ดํ
์ด์ ๊ณต์ ๋ฌธ์์ ์๊ฐ๋ MessageChatMemoryAdvisor๋ฅผ ์ ์ฉํด๋ณด๋ฉด, ์ด๋ฆ์ ๊ธฐ์ตํ๊ณ ์์์ ๋ณผ ์ ์์ต๋๋ค. (์ธ๋ฉ๋ชจ๋ฆฌ์ด๊ธฐ ๋๋ฌธ์ ์์ ํ ์ข ๋ฃ ํ ์ฌ์คํํ๋ฉด ๊ธฐ์ตํ์ง ๋ชปํฉ๋๋ค.)
package com.example.demo;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder
.defaultAdvisors(
new MessageChatMemoryAdvisor(new InMemoryChatMemory())
)
.build();
}
@GetMapping("/")
public String home(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}
์ถ๊ฐ๋ก ๋ก๊น ์ ํ ์ ์๋ SimpleLoggerAdvisor๋ฅผ ์ ์ฉํด๋ด ์๋ค.
SimpleLoggerAdvisor ์ ์ฉ
SimpleLoggerAdvisor๋ ์ด ๊ตฌํ์ฒด๋ง ๊ธฐ์กด์ ์ฝ๋์ ๋ถ์ด๋ฉด ๋ฉ๋๋ค.
package com.example.demo;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder
.defaultAdvisors(
new SimpleLoggerAdvisor(),
new MessageChatMemoryAdvisor(new InMemoryChatMemory())
)
.build();
}
@GetMapping("/")
public String home(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}
์ ํ๋ฆฌ์ผ์ด์ ๋ก๊ทธ ๋ ๋ฒจ์ DEBUG๋ก ์ค์ ํ๋ฉด, ์๋์ ๊ฐ์ด ์์ฒญ๊ณผ ์๋ต์ ๋ํ ๋ก๊น ์ด ์งํ๋จ์ ๋ณผ ์ ์์ต๋๋ค.
Reference
'๐ ๊ณต์ ๋ฌธ์ ๋ฒ์ญ > Spring AI (2025 Renewal)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[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 #2] Chat Client API (0) | 2025.04.04 |
[Spring AI 2025 Renewal #1] Spring AI๋ ๋ฌด์์ธ๊ฐ? (0) | 2025.04.04 |