你是否遇到过这样的情况:精心设计的 AI 应用,在面对稍微复杂点的问题时,给出的答案却驴唇不对马嘴?感觉它好像“看了一眼就答”,根本没仔细“阅读理解”。
别急,今天就为你介绍一个能显著提升大模型推理能力的技巧——Re-Reading(重读),简称 Re2。这个方法有 论文 背书,效果显著。
更棒的是,在 Spring AI 中,我们可以通过 Advisor(顾问) 模式,优雅地实现这一功能,让你的 AI 在回答前真正做到“三思而后行”。
Re2 的原理出奇地简单:让模型把问题再读一遍。
我们只需要将用户的原始问题({Input_Query}
)通过 Prompt 改造为以下格式:
{Input_Query}
Read the question again: {Input_Query}
通过这种方式,强制模型在生成答案前重新审视问题,从而有效减少误解,提高复杂推理任务的准确率。
💡 友情提示:这种方法虽然能提升效果,但因为输入长度翻倍,API 调用成本也会随之翻倍。因此,在面向 C 端的、成本敏感的应用中请谨慎使用!
在 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; // 设置为高优先级
}
}
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
,这里总结了几个最佳实践:
getOrder()
控制 Advisor 的执行顺序,确保逻辑正确。CallAroundAdvisor
和 StreamAroundAdvisor
接口,让你的 Advisor 更通用。Reactor
的操作符进行精细控制。@Override
public 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");
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。