Spring AI 自定义 CallAdvisor 和 StreamAdvisor 示例

Spring AI 的 CallAdvisor 和 StreamAdvisor 提供了在 LLM 调用周围包装的钩子,适用于同步和流式场景。

Spring AI

Spring AI 1.0.0 引入了 advisor 接口——特别是 CallAdvisorStreamAdvisor,以帮助开发人员拦截和丰富模型交互,适用于同步和流式场景。这些 advisor 的作用类似于 Spring Framework 中的 Spring AOP(面向切面编程)模式。

内部,CallAdvisorStreamAdvisor 提供了在 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,以及我们可以使用 CallAdvisorStreamAdvisor 创建自定义 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 的 CallAdvisorStreamAdvisor 是非常方便的工具,可以轻松扩展您的 AI 管道。无论您是构建聊天机器人、知识助手还是提示编排服务,advisor 都能让您以干净、可重用的方式进入模型生命周期。

祝您学习愉快!!

源代码下载

评论

订阅
通知
0 条评论
最多投票
最新 最旧
内联反馈
查看所有评论

关于我们

HowToDoInJava 提供 Java 和相关技术的教程和操作指南。

它还分享最佳实践、算法和解决方案以及经常被问到的面试题。

我们的博客

REST API 教程

关注我们