掘金 人工智能 07月09日 11:14
Spring AI 实现让你的 AI “三思而后行”
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文介绍了一种提升大模型推理能力的技巧——Re-Reading(重读),通过让模型重新审视问题来提高复杂推理任务的准确率。文章详细阐述了Re-Reading的原理,并在Spring AI中通过Advisor模式优雅地实现这一功能。通过自定义ReReadingAdvisor,可以拦截用户请求并自动应用Re2模式,增强推理能力,而无需改动业务代码。文章还提供了Advisor的最佳实践,包括保持单一职责、注意执行顺序、兼容流式与非流式等,帮助读者更好地应用Advisor。

🤔 Re-Reading (Re2) 的核心在于让模型重新审视问题,通过将原始问题重复一遍的方式,促使模型在生成答案前再次理解问题,从而减少误解,提高推理准确性。

💡 在 Spring AI 中,Advisor 是一种 AOP(面向切面编程)思想的体现,允许在不侵入核心业务逻辑的情况下,对 AI 的请求和响应进行拦截和增强,方便实现 Re-Reading 功能。

🛠️ 创建 ReReadingAdvisor 的关键在于拦截用户请求,修改请求内容,在原始问题中添加“Read the question again: {Input_Query}”,然后将修改后的请求传递给调用链的下一个环节。

✅ 为了更好地使用Advisor,建议遵循最佳实践,包括保持单一职责、注意执行顺序、兼容流式与非流式、保持高效、充分测试,以及善用Reactor和共享状态等。

你是否遇到过这样的情况:精心设计的 AI 应用,在面对稍微复杂点的问题时,给出的答案却驴唇不对马嘴?感觉它好像“看了一眼就答”,根本没仔细“阅读理解”。

别急,今天就为你介绍一个能显著提升大模型推理能力的技巧——Re-Reading(重读),简称 Re2。这个方法有 论文 背书,效果显著。

更棒的是,在 Spring AI 中,我们可以通过 Advisor(顾问) 模式,优雅地实现这一功能,让你的 AI 在回答前真正做到“三思而后行”。

什么是 Re-Reading (Re2)?

Re2 的原理出奇地简单:让模型把问题再读一遍

我们只需要将用户的原始问题({Input_Query})通过 Prompt 改造为以下格式:

{Input_Query}Read the question again: {Input_Query}

通过这种方式,强制模型在生成答案前重新审视问题,从而有效减少误解,提高复杂推理任务的准确率。

💡 友情提示:这种方法虽然能提升效果,但因为输入长度翻倍,API 调用成本也会随之翻倍。因此,在面向 C 端的、成本敏感的应用中请谨慎使用!

构建你的 Re2 Advisor

在 Spring AI 中,Advisor 是一种 AOP(面向切面编程)思想的体现,它允许我们在不侵入核心业务逻辑的情况下,对 AI 的请求和响应进行拦截和增强。

下面,我们来创建一个 ReReadingAdvisor,它会拦截用户请求并自动应用 Re2 模式。

/** * @author BNTang * @version 1.0 * @description 自定义 Re2 Advisor,通过让模型重读问题来提高其推理能力。 **/public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {    /**     * 在 AI 调用前执行,负责改写用户请求。     *     * @param advisedRequest 原始请求     * @return 应用了 Re2 模式的新请求     */    private AdvisedRequest before(AdvisedRequest advisedRequest) {        // 将原始查询存入参数,以便在模板中使用        Map<String, Object> advisedUserParams = new HashMap<>(advisedRequest.userParams());        advisedUserParams.put("re2_input_query", advisedRequest.userText());        // 使用新模板构建并返回 AdvisedRequest        return AdvisedRequest.from(advisedRequest)                .userText("""                        {re2_input_query}                        Read the question again: {re2_input_query}                        """)                .userParams(advisedUserParams)                .build();    }    /**     * 环绕处理非流式调用。     */    @NotNull    @Override    public AdvisedResponse aroundCall(@NotNull AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {        // 调用 before 方法修改请求,然后传递给调用链的下一个环节        return chain.nextAroundCall(this.before(advisedRequest));    }    /**     * 环绕处理流式调用。     */    @NotNull    @Override    public Flux<AdvisedResponse> aroundStream(@NotNull AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {        // 同样,调用 before 方法修改请求,然后传递给调用链        return chain.nextAroundStream(this.before(advisedRequest));    }    /**     * 返回 Advisor 的名称。     */    @NotNull    @Override    public String getName() {        return this.getClass().getSimpleName();    }    /**     * 定义 Advisor 的执行顺序,数值越小,优先级越高。     */    @Override    public int getOrder() {        return 0; // 设置为高优先级    }}

即插即用:在 ChatClient 中启用 Advisor

Advisor 写好了,用起来也非常简单。只需在构建 ChatClient 时,通过 .defaultAdvisors() 方法将其加入即可。

/** * App 构造函数,初始化聊天客户端。 * * @param ollamaChatModel 聊天模型实例 */public App(ChatModel ollamaChatModel) {    ChatMemory chatMemory = new InMemoryChatMemory();    chatClient = ChatClient.builder(ollamaChatModel)            .defaultSystem(SYSTEM_PROMPT)            .defaultAdvisors(                    new MessageChatMemoryAdvisor(chatMemory), // 记忆顾问                    new ReReadingAdvisor() // 启用 Re-Reading 顾问!            )            .build();}

现在,所有通过这个 chatClient 发出的请求,都会自动被 ReReadingAdvisor 处理,实现推理增强,而我们的业务代码无需做任何改动。是不是非常优雅?

Advisor 最佳实践清单

为了让你更好地驾驭 Advisor,这里总结了几个最佳实践:

    保持单一职责:每个 Advisor 应该只做一件事,比如日志、缓存、重试或像我们今天的 Re2。注意执行顺序:通过 getOrder() 控制 Advisor 的执行顺序,确保逻辑正确。兼容流式与非流式:尽可能同时实现 CallAroundAdvisorStreamAroundAdvisor 接口,让你的 Advisor 更通用。保持高效:避免在 Advisor 中执行耗时操作,以免阻塞整个调用链。充分测试:特别是边界情况,确保 Advisor 的健壮性。善用 Reactor(进阶):对于复杂的流式处理,可以利用 Reactor 的操作符进行精细控制。
@Overridepublic Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {    return Mono.just(advisedRequest)            .publishOn(Schedulers.boundedElastic())            .map(this::modifyRequest) // 请求前处理            .flatMapMany(chain::nextAroundStream)            .map(this::modifyResponse); // 响应后处理}
    共享状态(进阶):使用 advisedRequest.updateContext()advisedResponse.adviseContext() 在 Advisor 链中传递状态。
// 在 Advisor A 中更新上下文advisedRequest = advisedRequest.updateContext(context -> {context.put("my_key", "my_value");return context;});// 在 Advisor B 中读取上下文Object value = advisedResponse.adviseContext().get("my_key");

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Re-Reading Spring AI 大模型推理 Advisor
相关文章