Spring AI 1.0.0 引入了 advisor 接口——特别是 CallAdvisor 和 StreamAdvisor,以帮助开发人员拦截和丰富模型交互,适用于同步和流式场景。这些 advisor 的作用类似于 Spring Framework 中的 Spring AOP(面向切面编程)模式。
内部,CallAdvisor 和 StreamAdvisor 提供了在 LLM 调用周围包装的钩子,适用于同步和流式场景。 本文提供了一个完整的示例,演示了如何实现这些接口以及如何使用重写的方法 adviseCall() 和 adviseStream()。
1. CallAdvisor 和 StreamAdvisor API
CallAdvisor 允许我们拦截到 LLM 的阻塞/同步模型调用的前后。在 advisecall() 方法中,我们可以修改输入、记录数据、处理错误和操作输出。
public interface CallAdvisor extends Advisor {
ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain);
}
使用 StreamAdvisor 接口可以对来自 LLM 模型的流式输出进行类似的修改。
public interface StreamAdvisor extends Advisor {
Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain);
}
2. Advisor 生命周期和组合
Spring AI 支持可组合的 advisor 链。这意味着您可以注册多个 advisor,它们将按顺序调用。它遵循装饰器模式,其中每个 advisor 包装下一个。
链条如下:
客户端 -> Advisor1 -> Advisor2 -> … -> ModelCall
Spring AI API 提供了几个内置的 advisor,以及我们可以使用 CallAdvisor 和 StreamAdvisor 创建自定义 advisor。然后我们可以像以下这样使用这些 advisor:
String responseContent = chatClient.prompt()
.user("... message...")
.advisors(advisor1)
.advisors(advisor2)
.advisors(customAdvisor)
.advisors(advisor3)
.call()
.content();
3. 创建自定义 CallAdvisor 和 StreamAdvisor
创建一个类并实现 CallAdvisor 和 StreamAdvisor 接口。然后,实现类似于 Spring AOP 风格的方法。
在下面的示例中,我们正在修改提示并指示 LLM 像与 16 岁的孩子交谈一样回答。
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
public class CustomAdvisor implements CallAdvisor, StreamAdvisor {
private final static Logger logger = LoggerFactory.getLogger(CustomAdvisor.class);
@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
Assert.notNull(chatClientRequest, "the chatClientRequest cannot be null");
ChatClientRequest formattedChatClientRequest = augmentWithCustomInstructions(chatClientRequest);
return callAdvisorChain.nextCall(chatClientRequest);
}
@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
Assert.notNull(chatClientRequest, "the chatClientRequest cannot be null");
ChatClientRequest formattedChatClientRequest = augmentWithCustomInstructions(chatClientRequest);
return streamAdvisorChain.nextStream(chatClientRequest);
}
private static ChatClientRequest augmentWithCustomInstructions(ChatClientRequest chatClientRequest) {
String customInstructions = "Please respond as you are explaining it to a 16-year-old child. " +
"Use simple words and short sentences. " +
"If you don't know the answer, say 'I don't know'.";
Prompt augmentedPrompt = chatClientRequest.prompt()
.augmentUserMessage(userMessage -> userMessage.mutate()
.text(userMessage.getText() + System.lineSeparator() + customInstructions)
.build());
return ChatClientRequest.builder()
.prompt(augmentedPrompt)
.context(Map.copyOf(chatClientRequest.context()))
.build();
}
@Override
public String getName() {
return "CustomAdvisor";
}
@Override
public int getOrder() {
return Integer.MAX_VALUE;
}
}
避免在 StreamAdvisor 中进行任何类型的阻塞调用。
要调用 advisor,我们必须将 advisor 引用传递给 ChatClient 实例。
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CustomAdvisorExample implements CommandLineRunner {
@Value("${spring.ai.openai.api-key}")
String apiKey;
public static void main(String[] args) {
SpringApplication.run(CustomAdvisorExample.class, args);
}
@Override
public void run(String... args) {
OpenAiApi openAiApi = OpenAiApi.builder().apiKey(apiKey).build();
OpenAiChatModel chatModel = OpenAiChatModel.builder().openAiApi(openAiApi).build();
ChatClient chatClient = ChatClient
.builder(chatModel)
.build();
String responseContent = chatClient.prompt()
.user("My name is: Lokesh. Say my name in french and explain its meaning.")
.advisors(new CustomAdvisor())
.call()
.content();
System.out.printf("response: %s\n", responseContent);
}
}
4. 结论
Spring AI 的 CallAdvisor 和 StreamAdvisor 是非常方便的工具,可以轻松扩展您的 AI 管道。无论您是构建聊天机器人、知识助手还是提示编排服务,advisor 都能让您以干净、可重用的方式进入模型生命周期。
祝您学习愉快!!
评论