使用 Spring AI 顾问增强您的 AI 应用程序

工程 | Christian Tzolov | 2024年10月2日 | ...

在人工智能快速发展的今天,开发人员一直在寻求增强其 AI 应用程序的方法。 Spring AI 是一个用于构建 AI 驱动的 Java 框架,它引入了一项强大的功能:Spring AI 顾问

这些顾问可以增强您的 AI 应用程序,使其更具模块化、可移植性和易于维护性。

如果您不方便阅读文章,可以收听此**实验性**播客,该播客是根据博客内容**AI 生成**的

什么是 Spring AI 顾问?

从本质上讲,Spring AI 顾问是拦截并可能修改 AI 应用程序中聊天完成请求和响应流程的组件。该系统中的关键角色是**AroundAdvisor**,它允许开发人员动态地转换或利用这些交互中的信息。

使用顾问的主要好处包括

  1. **封装重复性任务**: 将常见的 GenAI 模式打包到可重用的单元中。
  2. **转换**: 增强发送到大型语言模型 (LLM) 的数据并格式化发送回客户端的响应。
  3. **可移植性**: 创建可重用的转换组件,这些组件适用于各种模型和用例。

顾问的工作原理

顾问系统作为一个链条运行,链条中的每个顾问都有机会处理传入的请求和传出的响应。以下是简化的流程

spring-ai-advisors-flow

  1. 根据用户的提示创建AdvisedRequest,以及一个空的advisor-context
  2. 链中的每个顾问都会处理请求,可能对其进行修改,然后将执行转发到链中的下一个顾问。或者,它可以选择通过不调用以调用下一个实体来阻止请求。
  3. 最后一个顾问将请求发送到聊天模型。
  4. 聊天模型的响应作为AdvisedResponse传递回顾问链,AdvisedResponse是原始ChatResponse和输入路径链中建议上下文的组合。
  5. 每个顾问都可以处理或修改响应。
  6. 将最终AdvisedResponse中增强的ChatResponse返回给客户端。

使用顾问

Spring AI 提供了一些预构建的顾问来处理常见场景和 Gen AI 模式

使用 ChatClient API,您可以注册管道所需的顾问

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        new MessageChatMemoryAdvisor(chatMemory), // chat-memory advisor
        new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()) // RAG advisor
    )
    .build();

String response = chatClient.prompt()
    // Set chat memory parameters at runtime
    .advisors(advisor -> advisor.param("chat_memory_conversation_id", "678")
            .param("chat_memory_response_size", 100))
    .user(userText)
    .call()
	.content();

实现您自己的顾问

顾问 API 包括用于非流式场景的 CallAroundAdvisorCallAroundAdvisorChain,以及用于流式场景的 StreamAroundAdvisorStreamAroundAdvisorChain。它还包括 AdvisedRequest 来表示未密封的提示请求数据,以及 AdvisedResponse 来表示聊天完成数据。AdvisedRequestAdvisedResponse 都有一个advise-context字段,用于在顾问链之间共享状态。

简单的日志记录顾问

创建自定义顾问非常简单。让我们实现一个简单的日志记录顾问来演示此过程

public class SimpleLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
    private static final Logger logger = LoggerFactory.getLogger(SimpleLoggerAdvisor.class);

    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }

    @Override
    public int getOrder() {
        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));
    }
}

此顾问在处理请求之前记录请求,并在收到响应后记录响应,从而提供对 AI 交互过程的有价值的见解。

aggregateAdvisedResponse(...) 实用程序将AdviseResponse块组合成单个AdvisedResponse,返回原始流并接受完成结果的Consumer回调。它保留原始内容和上下文。

重新阅读 (Re2) 顾问

让我们根据重新阅读 (Re2) 技术实现一个更高级的顾问,该技术受此 论文 的启发,它可以提高大型语言模型的推理能力

public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
    private static final String DEFAULT_USER_TEXT_ADVISE = """
        {re2_input_query}
        Read the question again: {re2_input_query}
        """;

    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }

    @Override
    public int getOrder() {
        return 0;
    }

    private AdvisedRequest before(AdvisedRequest advisedRequest) {

        String inputQuery = advisedRequest.userText(); //original user query

        Map<String, Object> params = new HashMap<>(advisedRequest.userParams());        
        params.put("re2_input_query", inputQuery);

        return AdvisedRequest.from(advisedRequest)
                .withUserText(DEFAULT_USER_TEXT_ADVISE)
                .withUserParams(params)
                .build();
    }

    @Override
    public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
        return chain.nextAroundCall(before(advisedRequest));
    }

    @Override
    public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
        return chain.nextAroundStream(before(advisedRequest));
    }
}

此顾问修改输入查询以包含“重新阅读”步骤,这可能会提高 AI 模型对问题的理解和推理能力。

高级主题

Spring AI 的高级主题涵盖了顾问管理的重要方面,包括订单控制状态共享流式处理能力。顾问执行顺序由 getOrder() 方法决定。顾问之间的状态共享通过共享的 advise-context 对象启用,从而方便复杂的多顾问场景。系统支持流式和非流式顾问,允许处理完整的请求和响应,或使用响应式编程概念处理连续的数据流。

控制顾问顺序

链中顾问的顺序至关重要,由getOrder()方法决定。顺序值较低的顾问先执行。由于顾问链是一个栈,链中的第一个顾问是最后一个处理请求和第一个处理响应的顾问。如果要确保某个顾问最后执行,请将其顺序设置为接近Ordered.LOWEST_PRECEDENCE的值,反之,要先执行,请将其顺序设置为接近Ordered.HIGHEST_PRECEDENCE的值。如果有多个顾问具有相同的顺序值,则执行顺序不保证。

使用 AdvisorContext 进行状态共享

AdvisedRequestAdvisedResponse都共享一个 advise-context 对象。您可以使用advise-context在链中的顾问之间共享状态,并构建涉及多个顾问的更复杂的处理场景。

流式与非流式

Spring AI 支持流式和非流式顾问。非流式顾问使用完整的请求和响应,而流式顾问使用响应式编程概念(例如,响应的 Flux)处理连续的数据流。

对于流式顾问,需要注意的是,单个AdvisedResponse实例仅表示整个Flux<AdvisedResponse>响应的一部分。相比之下,对于非流式顾问,AdvisedResponse包含完整的响应。

最佳实践

  1. 保持顾问专注于特定任务,以提高模块化。
  2. 在必要时,使用advise-context在顾问之间共享状态。
  3. 实现顾问的流式和非流式版本,以获得最大的灵活性。
  4. 仔细考虑链中顾问的顺序,以确保数据流正确。

结论

Spring AI 顾问提供了一种强大而灵活的方式来增强您的 AI 应用程序。通过利用此 API,您可以创建更复杂、可重用和可维护的 AI 组件。无论您是在实现自定义逻辑、管理对话历史还是改进模型推理,顾问都提供了一种简洁高效的解决方案。

我们鼓励您在您的项目中尝试 Spring AI 顾问,并将您的自定义实现与社区共享。可能性是无限的,您的创新可以帮助塑造 AI 应用程序开发的未来!

编码愉快,愿您的 AI 应用程序更加智能和响应迅速!

获取 Spring 新闻通讯

与 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以助您快速进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部