使用 Spring AI Advisors 为您的 AI 应用注入超强动力

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

在快速发展的人工智能世界中,开发者不断寻求增强其 AI 应用的方法。Spring AI,一个用于构建 AI 驱动应用的 Java 框架,引入了一个强大的特性:Spring AI Advisors

这些 Advisor 可以极大地增强您的 AI 应用,使其更加模块化、可移植且易于维护。

如果不方便阅读本文,您可以收听这个实验性播客,它是根据博客内容AI 生成的

什么是 Spring AI Advisors?

其核心在于,Spring AI Advisors 是拦截并可能修改 AI 应用中聊天补全请求和响应流程的组件。该系统中的关键参与者是 AroundAdvisor,它允许开发者在这些交互中动态转换或利用信息。

使用 Advisors 的主要优势包括

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

Advisors 的工作原理

Advisor 系统以链式方式运作,序列中的每个 Advisor 都有机会处理传入请求和传出响应。以下是一个简化流程:

spring-ai-advisors-flow

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

使用 Advisors

Spring AI 提供了几个预构建的 Advisors,用于处理常见场景和生成式 AI 模式

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

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();

实现您自己的 Advisor

Advisor API 包括用于非流式场景的 CallAroundAdvisorCallAroundAdvisorChain,以及用于流式场景的 StreamAroundAdvisorStreamAroundAdvisorChain。它还包括用于表示未封装的 Prompt 请求数据的 AdvisedRequest 和用于聊天补全数据的 AdvisedResponseAdvisedRequestAdvisedResponse 都有一个 advise-context 字段,用于在整个 Advisor 链中共享状态。

简单日志记录 Advisor

创建自定义 Advisor 很简单。让我们来实现一个简单的日志记录 Advisor 来演示整个过程

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));
    }
}

这个 Advisor 会在请求处理前和响应接收后记录日志,为 AI 交互过程提供有价值的洞察。

aggregateAdvisedResponse(...) 工具将 AdvisedResponse 块组合成一个单一的 AdvisedResponse,返回原始流并接受一个 Consumer 回调来处理完成的结果。它保留了原始内容和上下文。

重读 (Re2) Advisor

让我们基于重读 (Re2) 技术实现一个更高级的 Advisor,该技术受这篇论文启发,可以提高大型语言模型的推理能力

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));
    }
}

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

高级主题

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

控制 Advisor 顺序

链中 Advisors 的顺序至关重要,它由 getOrder() 方法决定。order 值较低的 Advisors 首先执行。由于 Advisor 链是一个栈,链中的第一个 Advisor 是最后一个处理请求和第一个处理响应的。如果您想确保某个 Advisor 最后执行,请将其 order 值设置接近 Ordered.LOWEST_PRECEDENCE;反之,如果想使其首先执行,请将其 order 值设置接近 Ordered.HIGHEST_PRECEDENCE。如果多个 Advisors 的 order 值相同,则执行顺序无法保证。

使用 AdvisorContext 进行状态共享

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

流式与非流式

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

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

最佳实践

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

结论

Spring AI Advisors 提供了一种强大且灵活的方式来增强您的 AI 应用。通过利用这个 API,您可以创建更复杂、可复用且易于维护的 AI 组件。无论您是实现自定义逻辑、管理对话历史,还是改进模型推理,Advisors 都提供了一个清晰高效的解决方案。

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

编程愉快,愿您的 AI 应用越来越智能和响应迅速!

订阅 Spring 资讯

保持与 Spring 资讯的连接

订阅

抢先一步

VMware 提供培训和认证,助您快速提升。

了解更多

获取支持

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

了解更多

即将举办的活动

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

查看全部