Spring AI 實(shí)現(xiàn)讓你的 AI “三思而后行”
你是否遇到過(guò)這樣的情況:精心設(shè)計(jì)的 AI 應(yīng)用,在面對(duì)稍微復(fù)雜點(diǎn)的問(wèn)題時(shí),給出的答案卻驢唇不對(duì)馬嘴?感覺(jué)它好像“看了一眼就答”,根本沒(méi)仔細(xì)“閱讀理解”。
別急,今天就為你介紹一個(gè)能顯著提升大模型推理能力的技巧——Re-Reading(重讀),簡(jiǎn)稱 Re2。這個(gè)方法有 論文 背書(shū),效果顯著。
更棒的是,在 Spring AI 中,我們可以通過(guò) Advisor(顧問(wèn)) 模式,優(yōu)雅地實(shí)現(xiàn)這一功能,讓你的 AI 在回答前真正做到“三思而后行”。
什么是 Re-Reading (Re2)?
Re2 的原理出奇地簡(jiǎn)單:讓模型把問(wèn)題再讀一遍。
我們只需要將用戶的原始問(wèn)題({Input_Query})通過(guò) Prompt 改造為以下格式:
{Input_Query}
Read the question again: {Input_Query}
通過(guò)這種方式,強(qiáng)制模型在生成答案前重新審視問(wèn)題,從而有效減少誤解,提高復(fù)雜推理任務(wù)的準(zhǔn)確率。
?? 友情提示:這種方法雖然能提升效果,但因?yàn)檩斎腴L(zhǎng)度翻倍,API 調(diào)用成本也會(huì)隨之翻倍。因此,在面向 C 端的、成本敏感的應(yīng)用中請(qǐng)謹(jǐn)慎使用!
構(gòu)建你的 Re2 Advisor
在 Spring AI 中,Advisor 是一種 AOP(面向切面編程)思想的體現(xiàn),它允許我們?cè)诓磺秩牒诵臉I(yè)務(wù)邏輯的情況下,對(duì) AI 的請(qǐng)求和響應(yīng)進(jìn)行攔截和增強(qiáng)。
下面,我們來(lái)創(chuàng)建一個(gè) ReReadingAdvisor,它會(huì)攔截用戶請(qǐng)求并自動(dòng)應(yīng)用 Re2 模式。
/**
* @author BNTang
* @version 1.0
* @description 自定義 Re2 Advisor,通過(guò)讓模型重讀問(wèn)題來(lái)提高其推理能力。
**/
public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
/**
* 在 AI 調(diào)用前執(zhí)行,負(fù)責(zé)改寫用戶請(qǐng)求。
*
* @param advisedRequest 原始請(qǐng)求
* @return 應(yīng)用了 Re2 模式的新請(qǐng)求
*/
private AdvisedRequest before(AdvisedRequest advisedRequest) {
// 將原始查詢存入?yún)?shù),以便在模板中使用
Map<String, Object> advisedUserParams = new HashMap<>(advisedRequest.userParams());
advisedUserParams.put("re2_input_query", advisedRequest.userText());
// 使用新模板構(gòu)建并返回 AdvisedRequest
return AdvisedRequest.from(advisedRequest)
.userText("""
{re2_input_query}
Read the question again: {re2_input_query}
""")
.userParams(advisedUserParams)
.build();
}
/**
* 環(huán)繞處理非流式調(diào)用。
*/
@NotNull
@Override
public AdvisedResponse aroundCall(@NotNull AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
// 調(diào)用 before 方法修改請(qǐng)求,然后傳遞給調(diào)用鏈的下一個(gè)環(huán)節(jié)
return chain.nextAroundCall(this.before(advisedRequest));
}
/**
* 環(huán)繞處理流式調(diào)用。
*/
@NotNull
@Override
public Flux<AdvisedResponse> aroundStream(@NotNull AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
// 同樣,調(diào)用 before 方法修改請(qǐng)求,然后傳遞給調(diào)用鏈
return chain.nextAroundStream(this.before(advisedRequest));
}
/**
* 返回 Advisor 的名稱。
*/
@NotNull
@Override
public String getName() {
return this.getClass().getSimpleName();
}
/**
* 定義 Advisor 的執(zhí)行順序,數(shù)值越小,優(yōu)先級(jí)越高。
*/
@Override
public int getOrder() {
return 0; // 設(shè)置為高優(yōu)先級(jí)
}
}
即插即用:在 ChatClient 中啟用 Advisor
Advisor 寫好了,用起來(lái)也非常簡(jiǎn)單。只需在構(gòu)建 ChatClient 時(shí),通過(guò) .defaultAdvisors() 方法將其加入即可。
/**
* App 構(gòu)造函數(shù),初始化聊天客戶端。
*
* @param ollamaChatModel 聊天模型實(shí)例
*/
public App(ChatModel ollamaChatModel) {
ChatMemory chatMemory = new InMemoryChatMemory();
chatClient = ChatClient.builder(ollamaChatModel)
.defaultSystem(SYSTEM_PROMPT)
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory), // 記憶顧問(wèn)
new ReReadingAdvisor() // 啟用 Re-Reading 顧問(wèn)!
)
.build();
}
現(xiàn)在,所有通過(guò)這個(gè) chatClient 發(fā)出的請(qǐng)求,都會(huì)自動(dòng)被 ReReadingAdvisor 處理,實(shí)現(xiàn)推理增強(qiáng),而我們的業(yè)務(wù)代碼無(wú)需做任何改動(dòng)。是不是非常優(yōu)雅?
Advisor 最佳實(shí)踐清單
為了讓你更好地駕馭 Advisor,這里總結(jié)了幾個(gè)最佳實(shí)踐:
- 保持單一職責(zé):每個(gè) Advisor 應(yīng)該只做一件事,比如日志、緩存、重試或像我們今天的 Re2。
- 注意執(zhí)行順序:通過(guò)
getOrder()控制 Advisor 的執(zhí)行順序,確保邏輯正確。 - 兼容流式與非流式:盡可能同時(shí)實(shí)現(xiàn)
CallAroundAdvisor和StreamAroundAdvisor接口,讓你的 Advisor 更通用。 - 保持高效:避免在 Advisor 中執(zhí)行耗時(shí)操作,以免阻塞整個(gè)調(diào)用鏈。
- 充分測(cè)試:特別是邊界情況,確保 Advisor 的健壯性。
- 善用 Reactor(進(jìn)階):對(duì)于復(fù)雜的流式處理,可以利用
Reactor的操作符進(jìn)行精細(xì)控制。
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
return Mono.just(advisedRequest)
.publishOn(Schedulers.boundedElastic())
.map(this::modifyRequest) // 請(qǐng)求前處理
.flatMapMany(chain::nextAroundStream)
.map(this::modifyResponse); // 響應(yīng)后處理
}
- 共享狀態(tài)(進(jìn)階):使用
advisedRequest.updateContext()和advisedResponse.adviseContext()在 Advisor 鏈中傳遞狀態(tài)。
// 在 Advisor A 中更新上下文
advisedRequest = advisedRequest.updateContext(context -> {
context.put("my_key", "my_value");
return context;
});
// 在 Advisor B 中讀取上下文
Object value = advisedResponse.adviseContext().get("my_key");

浙公網(wǎng)安備 33010602011771號(hào)