AI實戰篇:Spring AI + 混元 手把手帶你實現企業級穩定可部署的AI業務智慧體

努力的小雨發表於2024-10-18

前言

在之前的內容中,我們詳細講解了Spring AI的基礎用法及其底層原理。如果還有小夥伴對此感到困惑,歡迎參考下面這篇文章,深入學習並進一步掌握相關知識:https://www.cnblogs.com/guoxiaoyu/p/18441709

今天,我們將重點關注AI在實際應用中的落地表現,特別是Spring AI如何能夠幫助企業實現功能最佳化以及推動AI與業務的深度融合。我們將以當前大廠廣泛追逐的智慧體賽道為切入點,探討其在實際場景中的應用。考慮到許多同學可能已經接觸過智慧體,以這一主題作為討論的基礎,能夠更有效地幫助大家理解相關概念和技術的實際操作與效果。

因此,在本章節中,我們將以智慧體為出發點,帶領大家輕鬆實現一個本地穩定且可部署的智慧體解決方案。在這一過程中,我將詳細介紹每一個步驟,確保大家能夠順利跟上。此外,在章節的最後,我會根據我的理解,分析這一方案與現有智慧體的優缺點,以幫助大家全面瞭解不同選擇的利弊。

準備工作

當然,Spring AI整合了許多知名公司的介面實現。如果你真的想使用OpenAI的介面,可以考慮國內的混元API。混元API相容OpenAI的介面規範,這意味著你可以直接使用OpenAI官方提供的SDK來呼叫混元的大模型。這一設計大大簡化了遷移過程,你只需將base_url和api_key替換為混元相關的配置,而無需對現有應用進行額外修改。這樣,你就能夠無縫地將您的應用切換到混元大模型,享受到強大的AI功能和支援。

申請API KEY

大家完全不必擔心,經過我親自測試,目前所有介面都能夠正常相容,並且沒有發現任何異常或問題。可以透過以下連結申請:混元API申請地址

image

請確保在您個人的賬戶下申請相關的API KEY。

image

請務必妥善儲存您的API KEY資訊,因為在後續使用過程中,這一資訊將會變得非常重要。

對接文件

在這裡,瞭解一些注意事項並不是強制性的,因為我們並不需要直接對接混元(Hunyuan)的介面。實際上,我們可以在Spring AI中直接使用相容OpenAI的介面,這樣能夠大大簡化我們的操作流程。如果您有興趣深入瞭解相關的API文件,可以自行查詢介面文件地址,裡面有詳盡的說明和指導:API介面文件

請大家特別注意,由於智慧體在執行時需要呼叫相關的外掛或工作流,因此支援函式回撥的模型僅限於以下三個。這意味著,除了這三個模型之外,其他模型都不具備這一支援功能。請確保在選擇模型時考慮這一點。

image

請大家留意,目前混元尚未推出預付費的大模型資源包,使用者只能進行併發包的預購。有關計費詳情,請參見下方圖示。

image

專案配置

接下來,我們將繼續使用之前的 Spring AI 演示專案,並對其進行必要的修改。具體需要調整的 Maven POM 依賴項如下所示:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>

如圖所示,在我第一次配置檔案時選擇了使用 functioncall 模型,因為它的成本相對較低。然而,後來我發現該模型在對系統提示詞的識別上表現並不理想,後面我都換成了pro模型,大家可以根據自己的具體需求和預算做出相應的選擇。

functioncall對提示詞不敏感但是對函式回撥的結果可以很好的解析,pro對提示詞敏感但是函式回撥的結果他不直接回答,一直輸出planner內容但就是不回覆使用者。後面會有詳細說明。

image

application.properties 檔案用於全域性配置,所有的 ChatClient 都會遵循這一設定。這樣做的一個顯著好處是,開發人員在程式碼層面無需進行任何修改,只需在 Maven 的 POM 檔案中更改相應的依賴項,即可輕鬆切換到不同的 AI 大模型廠商。這種靈活性不僅提高了專案的可維護性,還方便了模型的替換與升級。

Spring AI 智慧體構建

現在,假設你已經完成了所有的準備工作,我們可以開始構建屬於自己的智慧體。首先,我們將專注於單獨定製配置引數。之前提到過,application.properties 檔案是全域性設定,適用於所有的 ChatClient,但每個模型實際上都有自己特定的領域和應用場景。因此,我們首先需要配置如何為每個介面進行個性化定製,以確保模型的表現更加貼合實際的業務需求。

個性化配置模型

普通呼叫

首先,讓我們來觀察在正常情況下程式碼應該如何編寫:

@PostMapping("/ai-function")
ChatDataPO functionGenerationByText(@RequestParam("userInput")  String userInput) {
    String content = this.myChatClientWithSystem
            .prompt()
            .system("你是努力的小雨,一名 Java 服務端碼農,潛心研究著 AI 技術的奧秘。熱愛技術交流與分享,對開源社群充滿熱情。")
            .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;
}

如圖所示,在我們發起請求之前,如果提前設定一個斷點,我們就能夠在這一時刻檢視到 chatOptions 引數,這個引數代表了我們預設的配置設定。因此,我們的主要目標就是在傳送請求之前,探討如何對 chatOptions 引數進行有效的修改。

image

在對提示詞進行測試的過程中,我們發現 functioncall 模型對於 system 提示詞的響應效果並不顯著,似乎沒有發揮出預期的作用。然而,這個模型的一個顯著優點是它支援函式回撥功能(在前面的章節中已經詳細講解過),此外,與 pro 模型相比,functioncall 模型的使用費用也相對較低,這使得它在某些情況下成為一個更具成本效益的選擇。

image

特殊呼叫

為了使模型的回覆更加貼合提示詞的要求,我們可以對模型進行單獨配置。如果你希望對某一個特定方法進行調整,而不是採用像 application.properties 中的全域性設定,那麼可以透過自行修改相應的引數來實現。具體的配置方法如下所示:

//省略重複程式碼
OpenAiChatOptions openAiChatOptions = OpenAiChatOptions.builder()
        .withModel("hunyuan-pro").withTemperature(0.5f).build();
String content = this.myChatClientWithSystem
        .prompt()
        .system("你是努力的小雨,一名 Java 服務端碼農,潛心研究著 AI 技術的奧秘。熱愛技術交流與分享,對開源社群充滿熱情。")
        .user(userInput)
        .options(openAiChatOptions)
        .advisors(messageChatMemoryAdvisor)
        //省略重複程式碼
}

在此,我們只需簡單地配置相關的選項即可完成設定。接下來,我們可以在斷點的部分檢查相關的配置,以確保這些設定已經生效並正常執行。

image

同樣的寫法,例如,我們之前設定的 pro 模型相比於 function-call 模型在處理系統提示詞時顯得更加友好。

image

思考路徑

實際上,在絕大多數智慧體中,這些思考路徑並不會被顯示出來,只有百度那邊的智慧體系統會將其呈現給使用者。這些思考路徑都是由大模型生成並返回的,因此我並沒有在這裡進行額外的配置。實際上,我們也可以選擇返回這些路徑,相關的原始碼也在此處:

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;
        }
    }
//省略程式碼
}

如圖所示,目前我們僅僅進行了簡單的列印操作,並未實現訊息轉換器(message converter)。考慮到我們的業務系統並不需要將這些資訊展示給客戶,因此我們認為當前的實現方式已足夠滿足需求。

image

大家可以看下思考路徑的資訊列印結果如下所示:

org.springframework.web.client.DefaultRestClient [453] -| Writing [ChatCompletionRequest[messages=[ChatCompletionMessage[

省略其他, 關鍵程式碼如下:

role=SYSTEM, name=null, toolCallId=null, toolCalls=null, refusal=null], ChatCompletionMessage[rawContent=長春的天氣咋樣?, role=USER, name=null, toolCallId=null, toolCalls=null, refusal=null], ChatCompletionMessage[rawContent=使用'CurrentWeather'功能來獲取長春的天氣情況。使用者想要知道長春當前的天氣情況。使用者的請求是關於獲取特定地點的天氣資訊,這與工具提供的'CurrentWeather'功能相匹配。

,##省略其他

配置外掛

我之前在影片中詳細講解了智慧體如何建立自定義外掛。在這次的實踐中,我們將繼續利用百度天氣外掛來獲取實時的天氣資訊。不過,與之前不同的是,這一次我們將把這一功能整合到Spring AI專案中。

資料庫配置

每個業務系統通常都會配備自有資料庫,以便更好地服務使用者。為了演示這一點,我們將建立一個MySQL示例,具體內容是獲取地區編碼值,並將其傳遞給API進行呼叫。在這個過程中,你可以透過外掛對資料庫進行各種操作,但在此我們主要專注於查詢的演示。

本次示例中,我將繼續使用騰訊雲輕量應用伺服器來搭建一個MySQL單機環境。在成功搭建環境後,我們將繼續進行後續操作。請確保在開始之前,所有必要的配置和設定都已完成,以便順利進行資料庫的查詢和API的呼叫。

image

以下是與相關配置有關的POM檔案依賴項:

<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>

資料庫連線配置資訊如下:

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

匯入資料

我已經成功完成了百度地圖提供的資料匯入工作,具體情況請參見下圖所示:

image

運算元據庫

接下來,您只需在外掛類內部直接進行資料庫操作即可。關於 SearchHttpAK 實體類,您可以直接從百度地圖提供的 Java SDK 中複製,無需額外說明。同時,請注意,areaInfoPOMapper 需要您在配置類中自行進行 Bean 注入,以確保其正常使用。

public class BaiDuWeatherService implements Function<Request, Response> {

    AreaInfoPOMapper areaInfoPOMapper;
    
    public BaiDuWeatherService(AreaInfoPOMapper areaInfoPOMapper) {
        this.areaInfoPOMapper = areaInfoPOMapper;
    }
    @JsonClassDescription("location:城市地址,例如:長春市")
    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 = "天氣資訊以獲取完畢,請你整理資訊,以清晰易懂的方式回覆使用者:" + snCal.requestGetAKForPlugins(params);
            log.info("reslut:{}", reslut);
        } catch (Exception e) {
            //此返回慎用,會導致無線呼叫工具鏈,所以請自行設定好次數或者直接返回錯誤即可。
            //reslut = "本次呼叫失敗,請重新呼叫CurrentWeather!";
            reslut = "本次呼叫失敗了!";
    }
    return new Response(reslut);
}

無論此次操作是否成功,都請務必避免讓大模型自行再次發起呼叫。這樣做可能會導致程式陷入死迴圈,從而影響系統的穩定性和可靠性。務必要確保在操作結束後進行適當的控制和管理,以防止這種情況發生。

image

外掛呼叫

透過這種方式,當我們再次詢問關於長春的天氣時,大模型將能夠有效地利用外掛返回的資料,以準確且及時地回答我們的問題。

image

在之前的討論中,我們提到過Pro模型對系統提示詞非常敏感。然而,需要注意的是,它並不會直接最佳化返回的回撥結果。

image

為了確保系統的響應符合預期,這裡建議再次使用系統提示詞進行限制和指導。透過明確的提示詞,我們可以更好地控制模型的輸出。

請將工具返回的資料格式化後以友好的方式回覆使用者的問題。

最佳化後,返回結果正常:

image

工作流配置

在這裡,我將不再演示Spring AI中的工作流,實際上,我們的某些外掛所編寫的業務邏輯本質上就構成了一個工作流的邏輯框架。接下來,我想重點講解如何利用第三方工作流工具來快速滿足業務需求。

整合第三方工作流

在考慮使用Spring AI實現智慧體功能時,我們不應輕易拋棄第三方視覺化平臺。整合這些第三方工作流可以幫助我們快速實現所需的功能,尤其是在開發過程中,編寫Java程式碼的要求往往繁瑣且複雜,一個簡單的需求可能需要涉及多個實體類的建立與維護。相較之下,某些簡單的業務邏輯透過第三方工作流來實現,無疑能提升我們的開發效率,減少不必要的工作量。

以Coze智慧體平臺為例,我們可以首先專注於編寫一個高效的工作流。這個工作流的主要目標是為使用者提供全面的查詢服務,包括旅遊航班、火車時刻、酒店預訂等資訊。

image

我們需要在申請到API金鑰後,進行後續的對接工作,並仔細研究開發文件,以確保順利整合和實現所需的功能。

image

工作流外掛

根據以上資訊,我們可以將工作流呼叫封裝成外掛。實際上,對於智慧體平臺而言,工作流與外掛本質上都是以函式呼叫的形式存在,因此將工作流轉換為外掛的過程是相對簡單且直接的。

public class TravelPlanningService implements Function<RequestParamer, ResponseParamer> {

    @JsonClassDescription("dep_city:出發城市地址,例如長春市;arr_city:到達城市,例如北京市")
    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 {
          //這裡我已經封裝好了http呼叫
            reslut = cozeWorkFlow.getCoze("7423018070586064915",request);;
            log.info("reslut:{}", reslut);
        } catch (Exception e) {
            reslut = "本次呼叫失敗了!";
        }
        return new ResponseParamer(reslut);
    }
}

由於我們的RequestParamer中使用了Java 14引入的record記錄特性,而舊版本的Fastjson無法支援將其轉換為JSON格式,因此在專案中必須使用最新版本的Fastjson依賴。如果使用不相容的舊版本,將會導致功能無法正常執行或發生失敗。

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.31</version>
</dependency>

經過配置後,如果Coze外掛能夠正常執行,那麼我們就可以開始為混元大模型提供相應的回答。

image

工作流呼叫

我們已成功將該外掛整合到請求處理流程中,具體實現的程式碼如下所示:

//省略重複程式碼
.functions("CurrentWeather","TravelPlanning")
.call()
.content();
//省略重複程式碼

由於返回的資訊較為冗長,因此混元大模型的響應時間通常會顯著延長。在這種情況下,我們的普通API呼叫可能會超時,導致無法成功獲取預期的結果。具體的錯誤資訊如下所示:

I/O error on POST request for "https://api.hunyuan.cloud.tencent.com/v1/chat/completions": timeout

retryTemplate超時修復

我們需要對當前的配置進行重新調整。起初,我認為問題出在retryTemplate的配置上,因為我們在之前的討論中提到過這一點。然而,經過仔細檢查後,我發現retryTemplate僅負責重試相關的資訊配置,並沒有涉及到超時設定。為了進一步排查問題,我深入檢視了後面的原始碼,最終發現需要對RestClientAutoConfiguration類進行相應的修改。

值得一提的是,RestClientAutoConfiguration類提供了定製化配置的選項,允許我們對請求的行為進行更細緻的控制。以下是該類的原始碼示例,展示了我們可以進行哪些具體調整:

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

因此,我們需要對restClientBuilder進行必要的修改。目前,restClientBuilder中的DEFAULTS配置全部為null,這意味著它正在使用預設的配置。而在我們呼叫coze工作流時,由於使用了okhttp類,內部實際上整合了okhttp,因此也遵循了okhttp的配置方式。

為了解決這一問題,我們可以直接調整ClientHttpRequestFactorySettings的配置,以設定我們所需的超時時間。具體的配置調整如下所示:

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

請注意,在剛才提到的思考路徑中,messageConverter也是在此處進行配置的。如果有特定的需求,您完全可以進行個性化的定製。關鍵的程式碼部分如下,這段程式碼將呼叫我們自定義的方法,以便實現定製化的邏輯。

如果您希望設定其他的個性化配置或資訊,可以參考以下示例進行調整。

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

至此,經過一系列的調整和配置,我們成功解決了超時問題。這意味著在呼叫hunyuan模型時,我們現在可以順利獲取到返回的結果。

image

私有知識庫

由於智慧體具備知識庫這一常見且重要的功能,我們也將實現這一部分。值得注意的是,hunyuan的API相容向量功能,這意味著我們可以直接利用知識庫來增強智慧體的能力。透過這一實現,我們不僅能夠享受到無限制的訪問許可權,還能夠進行高度的定製化,以滿足特定的業務需求。

更重要的是,這種設計使得我們在使用知識庫時具有完全的自主可控性,你無需擔心資料洩露的問題。

向量資料庫配置

接下來,我們將繼續整合Milvus,這是一個我們之前使用過的向量資料庫功能。雖然騰訊雲也提供了自己的向量資料庫解決方案,但目前尚未將其整合到Spring AI中。為了便於演示和開發,我們決定首先使用Milvus作為我們的向量資料庫。

為了順利完成這一整合,我們需要配置相應的依賴項,具體如下:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-milvus-store-spring-boot-starter</artifactId>
</dependency>

需要的配置檔案如下:

# 配置Milvus客戶端主機地址
spring.ai.vectorstore.milvus.client.host=
# 配置Milvus客戶端埠號
spring.ai.vectorstore.milvus.client.port=19530

# 配置Milvus資料庫名稱
spring.ai.vectorstore.milvus.databaseName=
# 配置Milvus集合名稱
spring.ai.vectorstore.milvus.collectionName=
# 如果沒有集合會預設建立一個,預設值為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 引數,model 當前固定為 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

在這裡,我們依然使用申請的混元大模型的API-key,因此無需再次進行配置。值得強調的是,這些引數的正確配置至關重要。如果未能妥善設定,將會導致系統在呼叫時出現錯誤。

基本操作

大多數智慧體平臺都將對知識庫進行全面開放,以便使用者能夠自由地進行檢視、修改、刪除和新增等操作。接下來,我們將演示如何進行這些操作:

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

以下是我個人的觀點:增刪查操作的基本實現已經完成。第三方智慧體平臺提供修改操作的原因在於,後續的流程中,都是在刪除資料後重新插入,這一操作是不可避免的,因為大家都有修改的需求。此外,值得注意的是,預設的向量資料庫並不支援顯示所有資料,這一限制促使我們需要引入相應的資料庫操作,以彌補這一缺陷,確保資料的完整性和可操作性。

為了更好地驗證這一過程的有效性,我提前呼叫了介面,上傳了一些知識庫的資料。接下來,我將展示這些資料的查詢效果。

image

這是我剛剛上傳的知識庫資訊。為了提高效率,接下來我將直接展示知識庫的RAG(Retrieval-Augmented Generation)檢索功能在我們的智慧體中的應用。

自動呼叫

根據我目前的觀察,所有智慧體平臺主要可以分為兩種實現方式:自動呼叫和按需呼叫。大部分平臺的實現還是以自動呼叫為主,除非寫在了工作流中也是就我們的函式里,那就和上面的外掛一樣了,我就不講解了。今天,我將重點討論自動呼叫是如何實現的。

自動呼叫知識庫的實現依賴於Advisor介面,具體方法是在每次請求前構造一個額外的提示詞。目前,Spring AI已經實現了長期記憶的功能,其具體類為VectorStoreChatMemoryAdvisor。因此,我們可以直接參考該類的實現方式,以便構建一個符合我們需求的知識庫自動呼叫系統。

我們可以進行一次實現。由於我們的主要目標是在將參考資訊提供給大模型時,使其能夠更好地理解上下文,因此對於響應的增強部分可以直接忽略。這意味著我們不需要在此過程中對響應的內容進行額外的處理或最佳化,以下是具體的程式碼示例:

public class PromptChatKnowledageAdvisor implements RequestResponseAdvisor {

    private VectorStore vectorStore;
    private static final String userTextAdvise = """

            請使用以下參考資訊回答問題.如果沒有參考資訊,那麼請直接回答即可。

            ---------------------
            參考資訊如下:
            {memory}
            ---------------------

            """;

    public PromptChatKnowledageAdvisor(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }
    @Override
    public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
        // 1. 新增一段知識庫提示
        String advisedSystemText = request.userText() + System.lineSeparator() + this.userTextAdvise;
        List<Document> documents = vectorStore.similaritySearch(request.userText());
        // 2. 拼接知識庫資料
        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. 賦值提示詞引數
        AdvisedRequest advisedRequest = AdvisedRequest.from(request)
            .withSystemText(advisedSystemText)
            .withSystemParams(advisedParams) //知識庫RAG檢索資料
            .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;
    }

}

需要在配置類中透過構造器注入來傳遞相同的 VectorStore 例項。

@Bean
PromptChatKnowledageAdvisor promptChatKnowledageAdvisor(VectorStore vectorStore) {
    return new PromptChatKnowledageAdvisor(vectorStore);
}

接下來,我們只需在請求方式中新增相應的程式碼或配置,以便整合新功能。

//省略重複程式碼
.advisors(messageChatMemoryAdvisor,promptChatKnowledageAdvisor)
.functions("CurrentWeather","TravelPlanning")
.call()
.content();
//省略重複程式碼

這正是自動呼叫所帶來的顯著效果,所有操作都得到了完全的封裝,清晰明瞭且易於理解。

image

接下來,我們來看下第二種按需呼叫的方式,這種方法是透過使用外掛(即函式回撥)來實現的。在這種模式下,系統可以根據實際需要動態呼叫相應的外掛,以提供靈活而高效的功能支援。我們之前已經演示過兩個相關的外掛,因此在這裡就不再詳細展示。

線上部署

我決定不再單獨將其部署到伺服器上,而是採用本地啟動的方式來暴露介面。此外,我還特別製作了一個獨立的頁面,考慮到這部分內容並不是本章的重點,因此我將不對前端知識進行詳細講解。

為了更好地展示這些內容,我提供了相關的演示影片,供大家參考:

權衡利弊

首先,我想談談目前各大智慧體平臺的一些顯著優勢:

  1. 視覺化操作:這些平臺提供了直觀的視覺化介面,使得即使是初學者也能快速開發出適合自己的業務智慧體,從而更好地滿足自身的業務需求。
  2. 多樣的釋出渠道:許多平臺支援多種釋出渠道,如公眾號等,這對於新手來說非常友好。相比之下,單純配置伺服器後臺往往需要專業知識,而這些平臺則大大降低了入門門檻。
  3. 豐富的外掛商店:無論是哪家智慧體平臺,外掛的多樣性都至關重要。這些平臺通常提供官方和開發者建立的各種外掛,幫助使用者擴充套件功能,滿足不同的需求。
  4. 多元的工作流:工作流功能實際上與外掛的作用類似,只是名稱有所不同。對外部系統而言,這些工作流都透過API介面實現整合,提升了系統間的互操作性與靈活性。

世間萬物都有缺陷,智慧體也不例外。即使像Coze這樣的強大平臺,同樣存在一些不足之處。以下幾點尤為明顯:

  1. 功能異常處理:當智慧體出現功能異常時,即使你提交了工單,客服和技術人員解決問題的速度往往很慢。這種情況下,你只能無奈地等待,無法確定問題出在哪裡。如果只是個人使用者的問題,可能連排期都不會給予反饋。而如果是自己開發的智慧體,遇到錯誤時,你可以迅速定位問題,無論需求如何,都能隨時進行修復併發布新版本。
  2. 知識庫儲存限制:由於這些智慧體是面向廣大使用者的,因此知識庫的儲存額度往往受到限制,而且未來可能會開始收費。Coze已經逐步引入了不同的收費標準,各種收費標準讓你看都看不懂。在這種情況下,自己維護一個伺服器無疑更加划算。此外,當前各大雲服務商和國產資料庫均有向量資料庫的推薦,且通常會提供優惠政策,極具吸引力。
  3. 知識庫資料最佳化:各大智慧體平臺的知識庫管理方式各異,使用者需要花時間適應其操作方式。而自己維護向量資料庫的好處在於,所有的額外後設資料資訊都可以自由配置,能夠根據具體業務需求進行資訊過濾,從而更好地符合自身的業務標準。這是其他智慧體平臺所無法提供的靈活性。
  4. 費用不可控:對於企業而言,管理各種費用的可控性至關重要。然而,智慧體平臺的收費往往隨著流量的增加而不受控制,可能會出現亂收費的情況,使企業陷入被動局面。相比之下,自行開發智慧體時,可以自由更換模型,費用也在自己的掌控之中,無論是伺服器費用還是大模型費用,都能有效管理。
  5. 選擇性弱:智慧體平臺通常與自身企業繫結,限制了使用者的選擇自由。某一天,平臺可能會決定不再支援某個大模型,這樣一來,相關的工作流也需要全部更換,因為不同的大模型在回覆能力上存在顯著差異,導致使用者不得不重新適應。
  6. 等等.....

說了這麼多,並不是說Spring AI未來會完全取代智慧體平臺。畢竟,對於小眾客戶而言,通常缺乏開發和維護人員去管理程式碼。因此,未來的趨勢很可能是這兩者相輔相成。智慧體平臺的開發速度和能力能夠基本滿足業務中80%的需求,這一原則與大廠所踐行的二八法則不謀而合。而剩下的20%則可能需要公司內部自行開發智慧體平臺來彌補,這一比例甚至有可能更高。

因此,掌握相關技術才是企業在這一變革中最為關鍵的因素。擁有技術能力將使企業在選擇和使用智慧體平臺時更加靈活,能夠根據自身的具體需求進行定製和最佳化。同時,我也希望混元大模型能夠儘快相容OpenAI的介面,或者融入Spring AI的大家庭,這樣將為使用者提供更多的選擇與靈活性。

總結

今天,我們深入探討了Spring AI在智慧體構建中的實際應用,特別是在企業環境中的價值與效能。透過逐步實現一個本地部署的智慧體解決方案,我們不僅展示了Spring AI的靈活性與易用性,還強調了它在推動AI技術與業務深度融合方面的潛力。

智慧體的核心在於其能夠高效處理複雜的業務需求,而這一切的實現離不開合理的架構設計與技術選型。透過Spring AI的整合,我們可以靈活地呼叫不同的API,不論是使用國內的混元API還是其他主流的AI介面,開發者都能在專案中快速切換,確保系統的可維護性與擴充套件性。這一特性不僅提升了開發效率,還使得企業在面對市場需求變化時能夠快速反應,靈活調整技術路線。

我們在過程中涉及到的個性化配置和外掛呼叫,充分展示瞭如何將傳統的開發模式與現代AI技術相結合。透過自定義外掛與工作流,企業可以根據具體的業務需求,設計出更具針對性的智慧體,從而提高服務質量和客戶滿意度。例如,在天氣查詢的場景中,智慧體不僅能夠透過API獲取實時資料,還能將其與資料庫中的資訊相結合,實現精準而個性化的服務。這種深度的功能整合,不僅簡化了使用者的操作流程,也提高了系統的響應速度。

此外,我們還提到私有知識庫的整合,強調了資料安全與自主可控的重要性。利用向量資料庫如Milvus,企業不僅能夠高效管理海量資料,還能透過嵌入技術提升智慧體的智慧水平。這為企業在資訊保安與智慧財產權保護方面提供了更為堅實的保障,尤其是在當前資訊化快速發展的背景下,這一點顯得尤為重要。

總之,本文不僅僅是對Spring AI智慧體構建過程的闡述,更是對企業如何有效利用這一技術實現業務升級與轉型的深入思考。希望透過我們的探討,能為您在智慧體開發與應用中提供新的視角與啟示,助力您在未來的AI之路上走得更加穩健。

相關文章