Spring?AI?+?混元帶你實(shí)現(xiàn)企業(yè)級(jí)穩(wěn)定可部署的AI業(yè)務(wù)智能體
前言
在之前的內(nèi)容中,我們?cè)敿?xì)講解了Spring AI的基礎(chǔ)用法及其底層原理。如果還有小伙伴對(duì)此感到困惑,歡迎參考下面這篇文章,深入學(xué)習(xí)并進(jìn)一步掌握相關(guān)知識(shí):http://chabaoo.cn/program/3306627kt.htm
今天,我們將重點(diǎn)關(guān)注AI在實(shí)際應(yīng)用中的落地表現(xiàn),特別是Spring AI如何能夠幫助企業(yè)實(shí)現(xiàn)功能優(yōu)化以及推動(dòng)AI與業(yè)務(wù)的深度融合。我們將以當(dāng)前大廠廣泛追逐的智能體賽道為切入點(diǎn),探討其在實(shí)際場(chǎng)景中的應(yīng)用??紤]到許多同學(xué)可能已經(jīng)接觸過智能體,以這一主題作為討論的基礎(chǔ),能夠更有效地幫助大家理解相關(guān)概念和技術(shù)的實(shí)際操作與效果。
因此,在本章節(jié)中,我們將以智能體為出發(fā)點(diǎn),帶領(lǐng)大家輕松實(shí)現(xiàn)一個(gè)本地穩(wěn)定且可部署的智能體解決方案。在這一過程中,我將詳細(xì)介紹每一個(gè)步驟,確保大家能夠順利跟上。此外,在章節(jié)的最后,我會(huì)根據(jù)我的理解,分析這一方案與現(xiàn)有智能體的優(yōu)缺點(diǎn),以幫助大家全面了解不同選擇的利弊。
準(zhǔn)備工作
當(dāng)然,Spring AI集成了許多知名公司的接口實(shí)現(xiàn)。如果你真的想使用OpenAI的接口,可以考慮國(guó)內(nèi)的混元API?;煸狝PI兼容OpenAI的接口規(guī)范,這意味著你可以直接使用OpenAI官方提供的SDK來調(diào)用混元的大模型。這一設(shè)計(jì)大大簡(jiǎn)化了遷移過程,你只需將base_url和api_key替換為混元相關(guān)的配置,而無需對(duì)現(xiàn)有應(yīng)用進(jìn)行額外修改。這樣,你就能夠無縫地將您的應(yīng)用切換到混元大模型,享受到強(qiáng)大的AI功能和支持。
申請(qǐng)API KEY
大家完全不必?fù)?dān)心,經(jīng)過我親自測(cè)試,目前所有接口都能夠正常兼容,并且沒有發(fā)現(xiàn)任何異?;騿栴}。可以通過以下鏈接申請(qǐng):混元API申請(qǐng)地址
請(qǐng)確保在您個(gè)人的賬戶下申請(qǐng)相關(guān)的API KEY。
請(qǐng)務(wù)必妥善保存您的API KEY信息,因?yàn)樵诤罄m(xù)使用過程中,這一信息將會(huì)變得非常重要。
對(duì)接文檔
在這里,了解一些注意事項(xiàng)并不是強(qiáng)制性的,因?yàn)槲覀儾⒉恍枰苯訉?duì)接混元(Hunyuan)的接口。實(shí)際上,我們可以在Spring AI中直接使用兼容OpenAI的接口,這樣能夠大大簡(jiǎn)化我們的操作流程。如果您有興趣深入了解相關(guān)的API文檔,可以自行查找接口文檔地址,里面有詳盡的說明和指導(dǎo):API接口文檔
請(qǐng)大家特別注意,由于智能體在運(yùn)行時(shí)需要調(diào)用相關(guān)的插件或工作流,因此支持函數(shù)回調(diào)的模型僅限于以下三個(gè)。這意味著,除了這三個(gè)模型之外,其他模型都不具備這一支持功能。請(qǐng)確保在選擇模型時(shí)考慮這一點(diǎn)。
請(qǐng)大家留意,目前混元尚未推出預(yù)付費(fèi)的大模型資源包,用戶只能進(jìn)行并發(fā)包的預(yù)購。有關(guān)計(jì)費(fèi)詳情,請(qǐng)參見下方圖示。
項(xiàng)目配置
接下來,我們將繼續(xù)使用之前的 Spring AI 演示項(xiàng)目,并對(duì)其進(jìn)行必要的修改。具體需要調(diào)整的 Maven POM 依賴項(xiàng)如下所示:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> </dependency>
如圖所示,在我第一次配置文件時(shí)選擇了使用 functioncall 模型,因?yàn)樗某杀鞠鄬?duì)較低。然而,后來我發(fā)現(xiàn)該模型在對(duì)系統(tǒng)提示詞的識(shí)別上表現(xiàn)并不理想,后面我都換成了pro模型,大家可以根據(jù)自己的具體需求和預(yù)算做出相應(yīng)的選擇。
functioncall對(duì)提示詞不敏感但是對(duì)函數(shù)回調(diào)的結(jié)果可以很好的解析,pro對(duì)提示詞敏感但是函數(shù)回調(diào)的結(jié)果他不直接回答,一直輸出planner內(nèi)容但就是不回復(fù)用戶。后面會(huì)有詳細(xì)說明。
application.properties
文件用于全局配置,所有的 ChatClient 都會(huì)遵循這一設(shè)置。這樣做的一個(gè)顯著好處是,開發(fā)人員在代碼層面無需進(jìn)行任何修改,只需在 Maven 的 POM 文件中更改相應(yīng)的依賴項(xiàng),即可輕松切換到不同的 AI 大模型廠商。這種靈活性不僅提高了項(xiàng)目的可維護(hù)性,還方便了模型的替換與升級(jí)。
Spring AI 智能體構(gòu)建
現(xiàn)在,假設(shè)你已經(jīng)完成了所有的準(zhǔn)備工作,我們可以開始構(gòu)建屬于自己的智能體。首先,我們將專注于單獨(dú)定制配置參數(shù)。之前提到過,application.properties 文件是全局設(shè)置,適用于所有的 ChatClient,但每個(gè)模型實(shí)際上都有自己特定的領(lǐng)域和應(yīng)用場(chǎng)景。因此,我們首先需要配置如何為每個(gè)接口進(jìn)行個(gè)性化定制,以確保模型的表現(xiàn)更加貼合實(shí)際的業(yè)務(wù)需求。
個(gè)性化配置模型
普通調(diào)用
首先,讓我們來觀察在正常情況下代碼應(yīng)該如何編寫:
@PostMapping("/ai-function") ChatDataPO functionGenerationByText(@RequestParam("userInput") String userInput) { String content = this.myChatClientWithSystem .prompt() .system("你是努力的小雨,一名 Java 服務(wù)端碼農(nóng),潛心研究著 AI 技術(shù)的奧秘。熱愛技術(shù)交流與分享,對(duì)開源社區(qū)充滿熱情。") .user(userInput) .advisors(messageChatMemoryAdvisor) .functions("CurrentWeather") .call() .content(); log.info("content: {}", content); ChatDataPO chatDataPO = ChatDataPO.builder().code("text").data(ChildData.builder().text(content).build()).build();; return chatDataPO; }
如圖所示,在我們發(fā)起請(qǐng)求之前,如果提前設(shè)置一個(gè)斷點(diǎn),我們就能夠在這一時(shí)刻查看到 chatOptions
參數(shù),這個(gè)參數(shù)代表了我們默認(rèn)的配置設(shè)置。因此,我們的主要目標(biāo)就是在發(fā)送請(qǐng)求之前,探討如何對(duì) chatOptions
參數(shù)進(jìn)行有效的修改。
在對(duì)提示詞進(jìn)行測(cè)試的過程中,我們發(fā)現(xiàn) functioncall
模型對(duì)于 system
提示詞的響應(yīng)效果并不顯著,似乎沒有發(fā)揮出預(yù)期的作用。然而,這個(gè)模型的一個(gè)顯著優(yōu)點(diǎn)是它支持函數(shù)回調(diào)功能(在前面的章節(jié)中已經(jīng)詳細(xì)講解過),此外,與 pro
模型相比,functioncall
模型的使用費(fèi)用也相對(duì)較低,這使得它在某些情況下成為一個(gè)更具成本效益的選擇。
特殊調(diào)用
為了使模型的回復(fù)更加貼合提示詞的要求,我們可以對(duì)模型進(jìn)行單獨(dú)配置。如果你希望對(duì)某一個(gè)特定方法進(jìn)行調(diào)整,而不是采用像 application.properties 中的全局設(shè)置,那么可以通過自行修改相應(yīng)的參數(shù)來實(shí)現(xiàn)。具體的配置方法如下所示:
//省略重復(fù)代碼 OpenAiChatOptions openAiChatOptions = OpenAiChatOptions.builder() .withModel("hunyuan-pro").withTemperature(0.5f).build(); String content = this.myChatClientWithSystem .prompt() .system("你是努力的小雨,一名 Java 服務(wù)端碼農(nóng),潛心研究著 AI 技術(shù)的奧秘。熱愛技術(shù)交流與分享,對(duì)開源社區(qū)充滿熱情。") .user(userInput) .options(openAiChatOptions) .advisors(messageChatMemoryAdvisor) //省略重復(fù)代碼 }
在此,我們只需簡(jiǎn)單地配置相關(guān)的選項(xiàng)即可完成設(shè)置。接下來,我們可以在斷點(diǎn)的部分檢查相關(guān)的配置,以確保這些設(shè)置已經(jīng)生效并正常運(yùn)行。
同樣的寫法,例如,我們之前設(shè)置的 pro 模型相比于 function-call 模型在處理系統(tǒng)提示詞時(shí)顯得更加友好。
思考路徑
實(shí)際上,在絕大多數(shù)智能體中,這些思考路徑并不會(huì)被顯示出來,只有百度那邊的智能體系統(tǒng)會(huì)將其呈現(xiàn)給用戶。這些思考路徑都是由大模型生成并返回的,因此我并沒有在這里進(jìn)行額外的配置。實(shí)際上,我們也可以選擇返回這些路徑,相關(guān)的源代碼也在此處:
private void writeWithMessageConverters(Object body, Type bodyType, ClientHttpRequest clientRequest) throws IOException { //省略代碼 for (HttpMessageConverter messageConverter : DefaultRestClient.this.messageConverters) { if (messageConverter instanceof GenericHttpMessageConverter genericMessageConverter) { if (genericMessageConverter.canWrite(bodyType, bodyClass, contentType)) { logBody(body, contentType, genericMessageConverter); genericMessageConverter.write(body, bodyType, contentType, clientRequest); return; } } if (messageConverter.canWrite(bodyClass, contentType)) { logBody(body, contentType, messageConverter); messageConverter.write(body, contentType, clientRequest); return; } } //省略代碼 }
如圖所示,目前我們僅僅進(jìn)行了簡(jiǎn)單的打印操作,并未實(shí)現(xiàn)消息轉(zhuǎn)換器(message converter)??紤]到我們的業(yè)務(wù)系統(tǒng)并不需要將這些信息展示給客戶,因此我們認(rèn)為當(dāng)前的實(shí)現(xiàn)方式已足夠滿足需求。
大家可以看下思考路徑的信息打印結(jié)果如下所示:
org.springframework.web.client.DefaultRestClient [453] -| Writing [ChatCompletionRequest[messages=[ChatCompletionMessage[
省略其他, 關(guān)鍵代碼如下:
role=SYSTEM, name=null, toolCallId=null, toolCalls=null, refusal=null], ChatCompletionMessage[rawContent=長(zhǎng)春的天氣咋樣?, role=USER, name=null, toolCallId=null, toolCalls=null, refusal=null], ChatCompletionMessage[rawContent=使用'CurrentWeather'功能來獲取長(zhǎng)春的天氣情況。用戶想要知道長(zhǎng)春當(dāng)前的天氣情況。用戶的請(qǐng)求是關(guān)于獲取特定地點(diǎn)的天氣信息,這與工具提供的'CurrentWeather'功能相匹配。
,##省略其他
配置插件
我之前在視頻中詳細(xì)講解了智能體如何創(chuàng)建自定義插件。在這次的實(shí)踐中,我們將繼續(xù)利用百度天氣插件來獲取實(shí)時(shí)的天氣信息。不過,與之前不同的是,這一次我們將把這一功能集成到Spring AI項(xiàng)目中。
數(shù)據(jù)庫配置
每個(gè)業(yè)務(wù)系統(tǒng)通常都會(huì)配備自有數(shù)據(jù)庫,以便更好地服務(wù)用戶。為了演示這一點(diǎn),我們將創(chuàng)建一個(gè)MySQL示例,具體內(nèi)容是獲取地區(qū)編碼值,并將其傳遞給API進(jìn)行調(diào)用。在這個(gè)過程中,你可以通過插件對(duì)數(shù)據(jù)庫進(jìn)行各種操作,但在此我們主要專注于查詢的演示。
本次示例中,我將繼續(xù)使用騰訊云輕量應(yīng)用服務(wù)器來搭建一個(gè)MySQL單機(jī)環(huán)境。在成功搭建環(huán)境后,我們將繼續(xù)進(jìn)行后續(xù)操作。請(qǐng)確保在開始之前,所有必要的配置和設(shè)置都已完成,以便順利進(jìn)行數(shù)據(jù)庫的查詢和API的調(diào)用。
以下是與相關(guān)配置有關(guān)的POM文件依賴項(xiàng):
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.7</version> </dependency>
數(shù)據(jù)庫連接配置信息如下:
spring.datasource.url=jdbc:mysql://ip:3306/agent?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 spring.datasource.username=agent spring.datasource.password=password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
導(dǎo)入數(shù)據(jù)
我已經(jīng)成功完成了百度地圖提供的數(shù)據(jù)導(dǎo)入工作,具體情況請(qǐng)參見下圖所示:
操作數(shù)據(jù)庫
接下來,您只需在插件類內(nèi)部直接進(jìn)行數(shù)據(jù)庫操作即可。關(guān)于 SearchHttpAK
實(shí)體類,您可以直接從百度地圖提供的 Java SDK 中復(fù)制,無需額外說明。同時(shí),請(qǐng)注意,areaInfoPOMapper
需要您在配置類中自行進(jìn)行 Bean 注入,以確保其正常使用。
public class BaiDuWeatherService implements Function<Request, Response> { AreaInfoPOMapper areaInfoPOMapper; public BaiDuWeatherService(AreaInfoPOMapper areaInfoPOMapper) { this.areaInfoPOMapper = areaInfoPOMapper; } @JsonClassDescription("location:城市地址,例如:長(zhǎng)春市") public record Request(String location) {} public record Response(String weather) {} public Response apply(Request request) { SearchHttpAK snCal = new SearchHttpAK(); Map params = new LinkedHashMap<String, String>(); QueryWrapper<AreaInfoPO> queryWrapper = new QueryWrapper<>(); queryWrapper.like("city", request.location()); List<AreaInfoPO> areaInfoPOS = areaInfoPOMapper.selectList(queryWrapper); String reslut = ""; try { params.put("district_id", areaInfoPOS.get(0).getCityGeocode()); reslut = "天氣信息以獲取完畢,請(qǐng)你整理信息,以清晰易懂的方式回復(fù)用戶:" + snCal.requestGetAKForPlugins(params); log.info("reslut:{}", reslut); } catch (Exception e) { //此返回慎用,會(huì)導(dǎo)致無線調(diào)用工具鏈,所以請(qǐng)自行設(shè)置好次數(shù)或者直接返回錯(cuò)誤即可。 //reslut = "本次調(diào)用失敗,請(qǐng)重新調(diào)用CurrentWeather!"; reslut = "本次調(diào)用失敗了!"; } return new Response(reslut); }
無論此次操作是否成功,都請(qǐng)務(wù)必避免讓大模型自行再次發(fā)起調(diào)用。這樣做可能會(huì)導(dǎo)致程序陷入死循環(huán),從而影響系統(tǒng)的穩(wěn)定性和可靠性。務(wù)必要確保在操作結(jié)束后進(jìn)行適當(dāng)?shù)目刂坪凸芾?,以防止這種情況發(fā)生。
插件調(diào)用
通過這種方式,當(dāng)我們?cè)俅卧儐栮P(guān)于長(zhǎng)春的天氣時(shí),大模型將能夠有效地利用插件返回的數(shù)據(jù),以準(zhǔn)確且及時(shí)地回答我們的問題。
在之前的討論中,我們提到過Pro模型對(duì)系統(tǒng)提示詞非常敏感。然而,需要注意的是,它并不會(huì)直接優(yōu)化返回的回調(diào)結(jié)果。
為了確保系統(tǒng)的響應(yīng)符合預(yù)期,這里建議再次使用系統(tǒng)提示詞進(jìn)行限制和指導(dǎo)。通過明確的提示詞,我們可以更好地控制模型的輸出。
請(qǐng)將工具返回的數(shù)據(jù)格式化后以友好的方式回復(fù)用戶的問題。
優(yōu)化后,返回結(jié)果正常:
工作流配置
在這里,我將不再演示Spring AI中的工作流,實(shí)際上,我們的某些插件所編寫的業(yè)務(wù)邏輯本質(zhì)上就構(gòu)成了一個(gè)工作流的邏輯框架。接下來,我想重點(diǎn)講解如何利用第三方工作流工具來快速滿足業(yè)務(wù)需求。
集成第三方工作流
在考慮使用Spring AI實(shí)現(xiàn)智能體功能時(shí),我們不應(yīng)輕易拋棄第三方可視化平臺(tái)。集成這些第三方工作流可以幫助我們快速實(shí)現(xiàn)所需的功能,尤其是在開發(fā)過程中,編寫Java代碼的要求往往繁瑣且復(fù)雜,一個(gè)簡(jiǎn)單的需求可能需要涉及多個(gè)實(shí)體類的創(chuàng)建與維護(hù)。相較之下,某些簡(jiǎn)單的業(yè)務(wù)邏輯通過第三方工作流來實(shí)現(xiàn),無疑能提升我們的開發(fā)效率,減少不必要的工作量。
以Coze智能體平臺(tái)為例,我們可以首先專注于編寫一個(gè)高效的工作流。這個(gè)工作流的主要目標(biāo)是為用戶提供全面的查詢服務(wù),包括旅游航班、火車時(shí)刻、酒店預(yù)訂等信息。
我們需要在申請(qǐng)到API密鑰后,進(jìn)行后續(xù)的對(duì)接工作,并仔細(xì)研究開發(fā)文檔,以確保順利整合和實(shí)現(xiàn)所需的功能。
工作流插件
根據(jù)以上信息,我們可以將工作流調(diào)用封裝成插件。實(shí)際上,對(duì)于智能體平臺(tái)而言,工作流與插件本質(zhì)上都是以函數(shù)調(diào)用的形式存在,因此將工作流轉(zhuǎn)換為插件的過程是相對(duì)簡(jiǎn)單且直接的。
public class TravelPlanningService implements Function<RequestParamer, ResponseParamer> { @JsonClassDescription("dep_city:出發(fā)城市地址,例如長(zhǎng)春市;arr_city:到達(dá)城市,例如北京市") public record RequestParamer(String dep_city, String arr_city) {} public record ResponseParamer(String weather) {} public ResponseParamer apply(RequestParamer request) { CozeWorkFlow cozeWorkFlow = new CozeWorkFlow<RequestParamer>(); Map params = new LinkedHashMap<String, String>(); String reslut = ""; try { //這里我已經(jīng)封裝好了http調(diào)用 reslut = cozeWorkFlow.getCoze("7423018070586064915",request);; log.info("reslut:{}", reslut); } catch (Exception e) { reslut = "本次調(diào)用失敗了!"; } return new ResponseParamer(reslut); } }
由于我們的RequestParamer中使用了Java 14引入的record記錄特性,而舊版本的Fastjson無法支持將其轉(zhuǎn)換為JSON格式,因此在項(xiàng)目中必須使用最新版本的Fastjson依賴。如果使用不兼容的舊版本,將會(huì)導(dǎo)致功能無法正常執(zhí)行或發(fā)生失敗。
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.31</version> </dependency>
經(jīng)過配置后,如果Coze插件能夠正常運(yùn)行,那么我們就可以開始為混元大模型提供相應(yīng)的回答。
工作流調(diào)用
我們已成功將該插件集成到請(qǐng)求處理流程中,具體實(shí)現(xiàn)的代碼如下所示:
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.31</version> </dependency>
由于返回的信息較為冗長(zhǎng),因此混元大模型的響應(yīng)時(shí)間通常會(huì)顯著延長(zhǎng)。在這種情況下,我們的普通API調(diào)用可能會(huì)超時(shí),導(dǎo)致無法成功獲取預(yù)期的結(jié)果。具體的錯(cuò)誤信息如下所示:
I/O error on POST request for "https://api.hunyuan.cloud.tencent.com/v1/chat/completions": timeout
retryTemplate超時(shí)修復(fù)
我們需要對(duì)當(dāng)前的配置進(jìn)行重新調(diào)整。起初,我認(rèn)為問題出在retryTemplate的配置上,因?yàn)槲覀冊(cè)谥暗挠懻撝刑岬竭^這一點(diǎn)。然而,經(jīng)過仔細(xì)檢查后,我發(fā)現(xiàn)retryTemplate僅負(fù)責(zé)重試相關(guān)的信息配置,并沒有涉及到超時(shí)設(shè)置。為了進(jìn)一步排查問題,我深入查看了后面的源碼,最終發(fā)現(xiàn)需要對(duì)RestClientAutoConfiguration類進(jìn)行相應(yīng)的修改。
值得一提的是,RestClientAutoConfiguration類提供了定制化配置的選項(xiàng),允許我們對(duì)請(qǐng)求的行為進(jìn)行更細(xì)致的控制。以下是該類的源碼示例,展示了我們可以進(jìn)行哪些具體調(diào)整:
@Bean @ConditionalOnMissingBean RestClientBuilderConfigurer restClientBuilderConfigurer(ObjectProvider<RestClientCustomizer> customizerProvider) { RestClientBuilderConfigurer configurer = new RestClientBuilderConfigurer(); configurer.setRestClientCustomizers(customizerProvider.orderedStream().toList()); return configurer; } @Bean @Scope("prototype") @ConditionalOnMissingBean RestClient.Builder restClientBuilder(RestClientBuilderConfigurer restClientBuilderConfigurer) { RestClient.Builder builder = RestClient.builder() .requestFactory(ClientHttpRequestFactories.get(ClientHttpRequestFactorySettings.DEFAULTS)); return restClientBuilderConfigurer.configure(builder); }
因此,我們需要對(duì)restClientBuilder進(jìn)行必要的修改。目前,restClientBuilder中的DEFAULTS配置全部為null,這意味著它正在使用默認(rèn)的配置。而在我們調(diào)用coze工作流時(shí),由于使用了okhttp類,內(nèi)部實(shí)際上集成了okhttp,因此也遵循了okhttp的配置方式。
為了解決這一問題,我們可以直接調(diào)整ClientHttpRequestFactorySettings的配置,以設(shè)置我們所需的超時(shí)時(shí)間。具體的配置調(diào)整如下所示:
@Bean RestClient.Builder restClientBuilder(RestClientBuilderConfigurer restClientBuilderConfigurer) { ClientHttpRequestFactorySettings defaultConfigurer = ClientHttpRequestFactorySettings.DEFAULTS .withReadTimeout(Duration.ofMinutes(5)) .withConnectTimeout(Duration.ofSeconds(30)); RestClient.Builder builder = RestClient.builder() .requestFactory(ClientHttpRequestFactories.get(defaultConfigurer)); return restClientBuilderConfigurer.configure(builder); }
請(qǐng)注意,在剛才提到的思考路徑中,messageConverter也是在此處進(jìn)行配置的。如果有特定的需求,您完全可以進(jìn)行個(gè)性化的定制。關(guān)鍵的代碼部分如下,這段代碼將調(diào)用我們自定義的方法,以便實(shí)現(xiàn)定制化的邏輯。
如果您希望設(shè)置其他的個(gè)性化配置或信息,可以參考以下示例進(jìn)行調(diào)整。
public RestClient.Builder configure(RestClient.Builder builder) { applyCustomizers(builder); return builder; } private void applyCustomizers(Builder builder) { if (this.customizers != null) { for (RestClientCustomizer customizer : this.customizers) { customizer.customize(builder); } } }
至此,經(jīng)過一系列的調(diào)整和配置,我們成功解決了超時(shí)問題。這意味著在調(diào)用hunyuan模型時(shí),我們現(xiàn)在可以順利獲取到返回的結(jié)果。
私有知識(shí)庫
由于智能體具備知識(shí)庫這一常見且重要的功能,我們也將實(shí)現(xiàn)這一部分。值得注意的是,hunyuan的API兼容向量功能,這意味著我們可以直接利用知識(shí)庫來增強(qiáng)智能體的能力。通過這一實(shí)現(xiàn),我們不僅能夠享受到無限制的訪問權(quán)限,還能夠進(jìn)行高度的定制化,以滿足特定的業(yè)務(wù)需求。
更重要的是,這種設(shè)計(jì)使得我們?cè)谑褂弥R(shí)庫時(shí)具有完全的自主可控性,你無需擔(dān)心數(shù)據(jù)泄露的問題。
向量數(shù)據(jù)庫配置
接下來,我們將繼續(xù)集成Milvus,這是一個(gè)我們之前使用過的向量數(shù)據(jù)庫功能。雖然騰訊云也提供了自己的向量數(shù)據(jù)庫解決方案,但目前尚未將其集成到Spring AI中。為了便于演示和開發(fā),我們決定首先使用Milvus作為我們的向量數(shù)據(jù)庫。
為了順利完成這一集成,我們需要配置相應(yīng)的依賴項(xiàng),具體如下:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-milvus-store-spring-boot-starter</artifactId> </dependency>
需要的配置文件如下:
# 配置Milvus客戶端主機(jī)地址 spring.ai.vectorstore.milvus.client.host= # 配置Milvus客戶端端口號(hào) spring.ai.vectorstore.milvus.client.port=19530 # 配置Milvus數(shù)據(jù)庫名稱 spring.ai.vectorstore.milvus.databaseName= # 配置Milvus集合名稱 spring.ai.vectorstore.milvus.collectionName= # 如果沒有集合會(huì)默認(rèn)創(chuàng)建一個(gè),默認(rèn)值為false spring.ai.vectorstore.milvus.initialize-schema=true # 配置向量嵌入維度 spring.ai.vectorstore.milvus.embeddingDimension=1024 # 配置索引類型 spring.ai.vectorstore.milvus.indexType=IVF_FLAT # 配置距離度量類型 spring.ai.vectorstore.milvus.metricType=COSINE
騰訊混元的embedding 接口目前僅支持 input 和 model 參數(shù),model 當(dāng)前固定為 hunyuan-embedding,dimensions 固定為 1024。
spring.ai.openai.embedding.base-url=https://api.hunyuan.cloud.tencent.com spring.ai.openai.embedding.options.model=hunyuan-embedding spring.ai.openai.embedding.options.dimensions=1024
在這里,我們依然使用申請(qǐng)的混元大模型的API-key,因此無需再次進(jìn)行配置。值得強(qiáng)調(diào)的是,這些參數(shù)的正確配置至關(guān)重要。如果未能妥善設(shè)置,將會(huì)導(dǎo)致系統(tǒng)在調(diào)用時(shí)出現(xiàn)錯(cuò)誤。
基本操作
大多數(shù)智能體平臺(tái)都將對(duì)知識(shí)庫進(jìn)行全面開放,以便用戶能夠自由地進(jìn)行查看、修改、刪除和新增等操作。接下來,我們將演示如何進(jìn)行這些操作:
@GetMapping("/ai/embedding") public Map embed(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) { EmbeddingResponse embeddingResponse = this.embeddingModel.embedForResponse(List.of(message)); return Map.of("embedding", embeddingResponse); } @GetMapping("/ai/addKnowledage") public boolean addKnowledage(@RequestParam(value = "meta-message") String message,@RequestParam(value = "vector-content") String content) { String uuid = UUID.randomUUID().toString(); DocumentInfoPO documentInfoPO = new DocumentInfoPO(); documentInfoPO.setVectorId(uuid); documentInfoPO.setMetaMessage(message); documentInfoPO.setVectorContent(content); documentInfoPOMapper.insert(documentInfoPO); List<Document> documents = List.of( new Document(uuid,content, Map.of("text", message))); vectorStore.add(documents); return true; } @GetMapping("/ai/selectKnowledage") public List<Document> selectKnowledage(@RequestParam(value = "vector-content") String content) { List<Document> result = vectorStore.similaritySearch(SearchRequest.query(content).withTopK(5).withSimilarityThreshold(0.9)); return result; } @GetMapping("/ai/deleteKnowledage") public Boolean deleteKnowledage(@RequestParam(value = "vector-id") String id) { Optional<Boolean> delete = vectorStore.delete(List.of(id)); return delete.get(); }
以下是我個(gè)人的觀點(diǎn):增刪查操作的基本實(shí)現(xiàn)已經(jīng)完成。第三方智能體平臺(tái)提供修改操作的原因在于,后續(xù)的流程中,都是在刪除數(shù)據(jù)后重新插入,這一操作是不可避免的,因?yàn)榇蠹叶加行薷牡男枨?。此外,值得注意的是,默認(rèn)的向量數(shù)據(jù)庫并不支持顯示所有數(shù)據(jù),這一限制促使我們需要引入相應(yīng)的數(shù)據(jù)庫操作,以彌補(bǔ)這一缺陷,確保數(shù)據(jù)的完整性和可操作性。
為了更好地驗(yàn)證這一過程的有效性,我提前調(diào)用了接口,上傳了一些知識(shí)庫的數(shù)據(jù)。接下來,我將展示這些數(shù)據(jù)的查詢效果。
這是我剛剛上傳的知識(shí)庫信息。為了提高效率,接下來我將直接展示知識(shí)庫的RAG(Retrieval-Augmented Generation)檢索功能在我們的智能體中的應(yīng)用。
自動(dòng)調(diào)用
根據(jù)我目前的觀察,所有智能體平臺(tái)主要可以分為兩種實(shí)現(xiàn)方式:自動(dòng)調(diào)用和按需調(diào)用。大部分平臺(tái)的實(shí)現(xiàn)還是以自動(dòng)調(diào)用為主,除非寫在了工作流中也是就我們的函數(shù)里,那就和上面的插件一樣了,我就不講解了。今天,我將重點(diǎn)討論自動(dòng)調(diào)用是如何實(shí)現(xiàn)的。
自動(dòng)調(diào)用知識(shí)庫的實(shí)現(xiàn)依賴于Advisor接口,具體方法是在每次請(qǐng)求前構(gòu)造一個(gè)額外的提示詞。目前,Spring AI已經(jīng)實(shí)現(xiàn)了長(zhǎng)期記憶的功能,其具體類為VectorStoreChatMemoryAdvisor。因此,我們可以直接參考該類的實(shí)現(xiàn)方式,以便構(gòu)建一個(gè)符合我們需求的知識(shí)庫自動(dòng)調(diào)用系統(tǒng)。
我們可以進(jìn)行一次實(shí)現(xiàn)。由于我們的主要目標(biāo)是在將參考信息提供給大模型時(shí),使其能夠更好地理解上下文,因此對(duì)于響應(yīng)的增強(qiáng)部分可以直接忽略。這意味著我們不需要在此過程中對(duì)響應(yīng)的內(nèi)容進(jìn)行額外的處理或優(yōu)化,以下是具體的代碼示例:
public class PromptChatKnowledageAdvisor implements RequestResponseAdvisor { private VectorStore vectorStore; private static final String userTextAdvise = """ 請(qǐng)使用以下參考信息回答問題.如果沒有參考信息,那么請(qǐng)直接回答即可。 --------------------- 參考信息如下: {memory} --------------------- """; public PromptChatKnowledageAdvisor(VectorStore vectorStore) { this.vectorStore = vectorStore; } @Override public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) { // 1. 添加一段知識(shí)庫提示 String advisedSystemText = request.userText() + System.lineSeparator() + this.userTextAdvise; List<Document> documents = vectorStore.similaritySearch(request.userText()); // 2. 拼接知識(shí)庫數(shù)據(jù) String data = documents.stream().map(d -> d.getMetadata().get("text").toString()).collect(Collectors.joining(",")); Map<String, Object> advisedParams = new HashMap<>(request.userParams()); advisedParams.put("memory", data); // 3. 賦值提示詞參數(shù) AdvisedRequest advisedRequest = AdvisedRequest.from(request) .withSystemText(advisedSystemText) .withSystemParams(advisedParams) //知識(shí)庫RAG檢索數(shù)據(jù) .build(); return advisedRequest; } @Override public ChatResponse adviseResponse(ChatResponse chatResponse, Map<String, Object> context) { //不需要修改任何東西 return chatResponse; } @Override public Flux<ChatResponse> adviseResponse(Flux<ChatResponse> fluxChatResponse, Map<String, Object> context) { //不需要修改任何東西 return fluxChatResponse; } }
需要在配置類中通過構(gòu)造器注入來傳遞相同的 VectorStore
實(shí)例。
@Bean PromptChatKnowledageAdvisor promptChatKnowledageAdvisor(VectorStore vectorStore) { return new PromptChatKnowledageAdvisor(vectorStore); }
接下來,我們只需在請(qǐng)求方式中添加相應(yīng)的代碼或配置,以便整合新功能。
//省略重復(fù)代碼 .advisors(messageChatMemoryAdvisor,promptChatKnowledageAdvisor) .functions("CurrentWeather","TravelPlanning") .call() .content(); //省略重復(fù)代碼
這正是自動(dòng)調(diào)用所帶來的顯著效果,所有操作都得到了完全的封裝,清晰明了且易于理解。
接下來,我們來看下第二種按需調(diào)用的方式,這種方法是通過使用插件(即函數(shù)回調(diào))來實(shí)現(xiàn)的。在這種模式下,系統(tǒng)可以根據(jù)實(shí)際需要?jiǎng)討B(tài)調(diào)用相應(yīng)的插件,以提供靈活而高效的功能支持。我們之前已經(jīng)演示過兩個(gè)相關(guān)的插件,因此在這里就不再詳細(xì)展示。
線上部署
我決定不再單獨(dú)將其部署到服務(wù)器上,而是采用本地啟動(dòng)的方式來暴露接口。此外,我還特別制作了一個(gè)獨(dú)立的頁面,考慮到這部分內(nèi)容并不是本章的重點(diǎn),因此我將不對(duì)前端知識(shí)進(jìn)行詳細(xì)講解。
為了更好地展示這些內(nèi)容,我提供了相關(guān)的演示視頻,供大家參考:
權(quán)衡利弊
首先,我想談?wù)勀壳案鞔笾悄荏w平臺(tái)的一些顯著優(yōu)勢(shì):
可視化操作:這些平臺(tái)提供了直觀的可視化界面,使得即使是初學(xué)者也能快速開發(fā)出適合自己的業(yè)務(wù)智能體,從而更好地滿足自身的業(yè)務(wù)需求。多樣的發(fā)布渠道:許多平臺(tái)支持多種發(fā)布渠道,如公眾號(hào)等,這對(duì)于新手來說非常友好。相比之下,單純配置服務(wù)器后臺(tái)往往需要專業(yè)知識(shí),而這些平臺(tái)則大大降低了入門門檻。豐富的插件商店:無論是哪家智能體平臺(tái),插件的多樣性都至關(guān)重要。這些平臺(tái)通常提供官方和開發(fā)者創(chuàng)建的各種插件,幫助用戶擴(kuò)展功能,滿足不同的需求。多元的工作流:工作流功能實(shí)際上與插件的作用類似,只是名稱有所不同。對(duì)外部系統(tǒng)而言,這些工作流都通過API接口實(shí)現(xiàn)集成,提升了系統(tǒng)間的互操作性與靈活性。
世間萬物都有缺陷,智能體也不例外。即使像Coze這樣的強(qiáng)大平臺(tái),同樣存在一些不足之處。以下幾點(diǎn)尤為明顯:
功能異常處理:當(dāng)智能體出現(xiàn)功能異常時(shí),即使你提交了工單,客服和技術(shù)人員解決問題的速度往往很慢。這種情況下,你只能無奈地等待,無法確定問題出在哪里。如果只是個(gè)人用戶的問題,可能連排期都不會(huì)給予反饋。而如果是自己開發(fā)的智能體,遇到錯(cuò)誤時(shí),你可以迅速定位問題,無論需求如何,都能隨時(shí)進(jìn)行修復(fù)并發(fā)布新版本。知識(shí)庫存儲(chǔ)限制:由于這些智能體是面向廣大用戶的,因此知識(shí)庫的存儲(chǔ)額度往往受到限制,而且未來可能會(huì)開始收費(fèi)。Coze已經(jīng)逐步引入了不同的收費(fèi)標(biāo)準(zhǔn),各種收費(fèi)標(biāo)準(zhǔn)讓你看都看不懂。在這種情況下,自己維護(hù)一個(gè)服務(wù)器無疑更加劃算。此外,當(dāng)前各大云服務(wù)商和國(guó)產(chǎn)數(shù)據(jù)庫均有向量數(shù)據(jù)庫的推薦,且通常會(huì)提供優(yōu)惠政策,極具吸引力。知識(shí)庫資料優(yōu)化:各大智能體平臺(tái)的知識(shí)庫管理方式各異,用戶需要花時(shí)間適應(yīng)其操作方式。而自己維護(hù)向量數(shù)據(jù)庫的好處在于,所有的額外元數(shù)據(jù)信息都可以自由配置,能夠根據(jù)具體業(yè)務(wù)需求進(jìn)行信息過濾,從而更好地符合自身的業(yè)務(wù)標(biāo)準(zhǔn)。這是其他智能體平臺(tái)所無法提供的靈活性。費(fèi)用不可控:對(duì)于企業(yè)而言,管理各種費(fèi)用的可控性至關(guān)重要。然而,智能體平臺(tái)的收費(fèi)往往隨著流量的增加而不受控制,可能會(huì)出現(xiàn)亂收費(fèi)的情況,使企業(yè)陷入被動(dòng)局面。相比之下,自行開發(fā)智能體時(shí),可以自由更換模型,費(fèi)用也在自己的掌控之中,無論是服務(wù)器費(fèi)用還是大模型費(fèi)用,都能有效管理。選擇性弱:智能體平臺(tái)通常與自身企業(yè)綁定,限制了用戶的選擇自由。某一天,平臺(tái)可能會(huì)決定不再支持某個(gè)大模型,這樣一來,相關(guān)的工作流也需要全部更換,因?yàn)椴煌拇竽P驮诨貜?fù)能力上存在顯著差異,導(dǎo)致用戶不得不重新適應(yīng)。等等.....
說了這么多,并不是說Spring AI未來會(huì)完全取代智能體平臺(tái)。畢竟,對(duì)于小眾客戶而言,通常缺乏開發(fā)和維護(hù)人員去管理代碼。因此,未來的趨勢(shì)很可能是這兩者相輔相成。智能體平臺(tái)的開發(fā)速度和能力能夠基本滿足業(yè)務(wù)中80%的需求,這一原則與大廠所踐行的二八法則不謀而合。而剩下的20%則可能需要公司內(nèi)部自行開發(fā)智能體平臺(tái)來彌補(bǔ),這一比例甚至有可能更高。
因此,掌握相關(guān)技術(shù)才是企業(yè)在這一變革中最為關(guān)鍵的因素。擁有技術(shù)能力將使企業(yè)在選擇和使用智能體平臺(tái)時(shí)更加靈活,能夠根據(jù)自身的具體需求進(jìn)行定制和優(yōu)化。同時(shí),我也希望混元大模型能夠盡快兼容OpenAI的接口,或者融入Spring AI的大家庭,這樣將為用戶提供更多的選擇與靈活性。
總結(jié)
今天,我們深入探討了Spring AI在智能體構(gòu)建中的實(shí)際應(yīng)用,特別是在企業(yè)環(huán)境中的價(jià)值與效能。通過逐步實(shí)現(xiàn)一個(gè)本地部署的智能體解決方案,我們不僅展示了Spring AI的靈活性與易用性,還強(qiáng)調(diào)了它在推動(dòng)AI技術(shù)與業(yè)務(wù)深度融合方面的潛力。
智能體的核心在于其能夠高效處理復(fù)雜的業(yè)務(wù)需求,而這一切的實(shí)現(xiàn)離不開合理的架構(gòu)設(shè)計(jì)與技術(shù)選型。通過Spring AI的集成,我們可以靈活地調(diào)用不同的API,不論是使用國(guó)內(nèi)的混元API還是其他主流的AI接口,開發(fā)者都能在項(xiàng)目中快速切換,確保系統(tǒng)的可維護(hù)性與擴(kuò)展性。這一特性不僅提升了開發(fā)效率,還使得企業(yè)在面對(duì)市場(chǎng)需求變化時(shí)能夠快速反應(yīng),靈活調(diào)整技術(shù)路線。
我們?cè)谶^程中涉及到的個(gè)性化配置和插件調(diào)用,充分展示了如何將傳統(tǒng)的開發(fā)模式與現(xiàn)代AI技術(shù)相結(jié)合。通過自定義插件與工作流,企業(yè)可以根據(jù)具體的業(yè)務(wù)需求,設(shè)計(jì)出更具針對(duì)性的智能體,從而提高服務(wù)質(zhì)量和客戶滿意度。例如,在天氣查詢的場(chǎng)景中,智能體不僅能夠通過API獲取實(shí)時(shí)數(shù)據(jù),還能將其與數(shù)據(jù)庫中的信息相結(jié)合,實(shí)現(xiàn)精準(zhǔn)而個(gè)性化的服務(wù)。這種深度的功能整合,不僅簡(jiǎn)化了用戶的操作流程,也提高了系統(tǒng)的響應(yīng)速度。
此外,我們還提到私有知識(shí)庫的集成,強(qiáng)調(diào)了數(shù)據(jù)安全與自主可控的重要性。利用向量數(shù)據(jù)庫如Milvus,企業(yè)不僅能夠高效管理海量數(shù)據(jù),還能通過嵌入技術(shù)提升智能體的智能水平。這為企業(yè)在信息安全與知識(shí)產(chǎn)權(quán)保護(hù)方面提供了更為堅(jiān)實(shí)的保障,尤其是在當(dāng)前信息化快速發(fā)展的背景下,這一點(diǎn)顯得尤為重要。
總之,本文不僅僅是對(duì)Spring AI智能體構(gòu)建過程的闡述,更是對(duì)企業(yè)如何有效利用這一技術(shù)實(shí)現(xiàn)業(yè)務(wù)升級(jí)與轉(zhuǎn)型的深入思考。希望通過我們的探討,能為您在智能體開發(fā)與應(yīng)用中提供新的視角與啟示,助力您在未來的AI之路上走得更加穩(wěn)健。
到此這篇關(guān)于Spring AI + 混元 手把手帶你實(shí)現(xiàn)企業(yè)級(jí)穩(wěn)定可部署的AI業(yè)務(wù)智能體 的文章就介紹到這了,更多相關(guān)Spring AI 混元AI業(yè)務(wù)智能體 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring Boot中利用JavaMailSender發(fā)送郵件的方法示例(附源碼)
- 深度解析Spring AI請(qǐng)求與響應(yīng)機(jī)制的核心邏輯
- Spring AI實(shí)現(xiàn)智能聊天模型
- 深入解析Spring?AI框架如何在Java應(yīng)用中實(shí)現(xiàn)智能化交互的關(guān)鍵
- Spring AI 入門學(xué)習(xí)指南
- Spring?AI?+?ollama?本地搭建聊天?AI?功能
- Spring?AI借助全局參數(shù)實(shí)現(xiàn)智能數(shù)據(jù)庫操作與個(gè)性化待辦管理
- Spring AI 文檔的提取、轉(zhuǎn)換、加載功能實(shí)現(xiàn)
- 如何使用spring-ws發(fā)布webservice服務(wù)
- 使用?Spring?AI?+?Ollama?構(gòu)建生成式?AI?應(yīng)用的方法
- Spring AI源碼分析流式回答(最新推薦)
相關(guān)文章
Java同步鎖synchronized用法的最全總結(jié)
這篇文章主要介紹了Java同步鎖synchronized用法的最全總結(jié),需要的朋友可以參考下,文章詳細(xì)講解了Java同步鎖Synchronized的使用方法和需要注意的點(diǎn),希望對(duì)你有所幫助2023-03-03SpringBoot?整合?Elasticsearch?實(shí)現(xiàn)海量級(jí)數(shù)據(jù)搜索功能
這篇文章主要介紹了SpringBoot?整合?Elasticsearch?實(shí)現(xiàn)海量級(jí)數(shù)據(jù)搜索,本文主要圍繞?SpringBoot?整合?ElasticSearch?接受數(shù)據(jù)的插入和搜索使用技巧,在實(shí)際的使用過程中,版本號(hào)尤其的重要,不同版本的?es,對(duì)應(yīng)的?api?是不一樣,需要的朋友可以參考下2022-07-07理解Java注解及Spring的@Autowired是如何實(shí)現(xiàn)的
今天通過本文帶領(lǐng)大家學(xué)習(xí)注解的基礎(chǔ)知識(shí),學(xué)習(xí)Spring的@Autowired是怎么實(shí)現(xiàn)的,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-07-07Java中反射的"暴破"機(jī)制(SetAccessible方法)詳解
這篇文章主要為大家詳細(xì)介紹了Java中反射的"暴破"機(jī)制,以及如何利用這一機(jī)制實(shí)現(xiàn)訪問非公有屬性,方法,和構(gòu)造器,文中示例代碼講解詳細(xì),感興趣的可以了解一下2022-08-08SpringMVC框架和SpringBoot項(xiàng)目中控制器的響應(yīng)結(jié)果深入分析
這篇文章主要介紹了SpringMVC框架和SpringBoot項(xiàng)目中控制器的響應(yīng)結(jié)果,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-12-12SpringBoot 分布式驗(yàn)證碼登錄方案示例詳解
為了防止驗(yàn)證系統(tǒng)被暴力破解,很多系統(tǒng)都增加了驗(yàn)證碼效驗(yàn),比較常見的就是圖片二維碼,業(yè)內(nèi)比較安全的是短信驗(yàn)證碼,當(dāng)然還有一些拼圖驗(yàn)證碼,加入人工智能的二維碼等等,我們今天的主題就是前后端分離的圖片二維碼登錄方案,感興趣的朋友一起看看吧2023-10-10