今天我們的Spring AI原始碼分析主題即將結束。我已經對自己感興趣的基本內容進行了全面的審視,並將這些分析分享給大家。如果你對這個主題感興趣,可以閱讀以下幾篇文章。每篇文章都層層遞進,深入探討相關內容。考慮到長文可能讓大家感到疲憊,我採用了逐步推進的方式,確保每一篇都簡明易懂,便於理解。希望能為你們提供有價值的參考!
Spring AI的基本用法:https://www.cnblogs.com/guoxiaoyu/p/18284842
Spring.3版本自動裝配機制的演變與實踐:https://www.cnblogs.com/guoxiaoyu/p/18384642
SpringBoot.3中的aot.factories到底有什麼用:https://www.cnblogs.com/guoxiaoyu/p/18434660
Spring AI的阻塞式請求與響應機制的核心邏輯:https://www.cnblogs.com/guoxiaoyu/p/18440488
Spring AI的流式回答原始碼分析:https://www.cnblogs.com/guoxiaoyu/p/18440684
今天我們的主題將聚焦於最後一步:如何將AI技術有效應用於Java程式中。眾所周知,Java是一種物件導向的程式語言,因此不論我們呼叫什麼AI介面,從業務的角度來看,它本質上只是一個介面,而AI則充當了一個第三方對接平臺。然而,值得注意的是,AI的聊天回覆往往不適用於物件,因為這些回覆無法直接返回格式化的JSON資料。這一問題導致Spring無法將其轉化為實體類,從而無法真正融入業務流程。
今天,我們將探討Spring AI框架是如何有效解決這一挑戰的。透過深入分析框架的設計和實現,我們希望為大家展示如何將AI能力順利整合到Java應用中,推動業務的進一步發展。
除此之外,function call 函式回撥也是AI技術的一個重要特性。那麼,Spring AI是如何應對這一挑戰的呢?今天,我們將深入探討這個問題,解析Spring AI框架如何有效處理函式回撥,從而增強AI與Java程式之間的互動能力。
實體化類
實體類在Java程式中扮演著不可或缺的角色,無論是進行內部操作,還是將資料返回給前端的RESTful介面,實體類都是業務中資訊傳遞的核心。在Spring AI框架中,我們可以有效地控制AI的回答,以確保其能夠正確對映到實體類。接下來,我們將探討Spring AI是如何實現這一功能的,基本用法如下:
@GetMapping("/ai-Entity")
ActorFilms generationByEntity() {
ActorFilms actorFilms = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class);
return actorFilms;
}
原始碼分析
在這裡,我們不再直接呼叫 content
方法,而是選擇使用 entity
方法作為返回型別。這一變化意味著我們需要重點關注 entity
的實現及其在整個流程中的作用。接下來,讓我們透過程式碼示例來深入分析這一關鍵部分:
public <T> T entity(Class<T> type) {
Assert.notNull(type, "the class must be non-null");
var boc = new BeanOutputConverter<T>(type);
return doSingleWithBeanOutputConverter(boc);
}
這裡使用了一個名為 BeanOutputConverter
的轉換器。接下來,我們來詳細檢視一下 doSingleWithBeanOutputConverter
方法的具體實現。
private <T> T doSingleWithBeanOutputConverter(StructuredOutputConverter<T> boc) {
var chatResponse = doGetObservableChatResponse(this.request, boc.getFormat());
var stringResponse = chatResponse.getResult().getOutput().getContent();
return boc.convert(stringResponse);
}
在這裡,我們要討論的 doGetObservableChatResponse
方法主要負責與第三方 API 的互動過程。由於我們在之前的講解中已經對聊天呼叫API方法的實現進行了詳細分析,因此這次我們就不再深入探討其具體內容,而是集中於方法的核心功能和應用場景。
實體類提示詞限制
在這裡,我們來檢視一下 boc.getFormat()
方法。這個方法返回一段提示詞,而這些提示詞會根據不同的型別而有所區別。為了更好地理解,我們可以具體分析一下單個 Bean 實體類所對應的提示詞格式。
具體如下:
public String getFormat() {
String template = """
Your response should be in JSON format.
Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.
Do not include markdown code blocks in your response.
Remove the ```json markdown from the output.
Here is the JSON Schema instance your output must adhere to:
```%s```
""";
return String.format(template, this.jsonSchema);
}
這其實非常簡單。透過使用提示詞來明確限制 AI 返回的格式,可以最大程度地確保其輸出符合我們的要求。這種方式使得 Spring 能夠有效地進行解析,而 jsonSchema 則僅僅是我們傳遞的實體類的各種資訊。
封裝實體類
boc.convert
方法負責將資料封裝成實體類的過程。具體來說,它會接收原始資料,並透過內部邏輯進行轉換,以生成符合我們定義的實體類結構。
從表面上看,我們可以清晰地看出該過程涉及到 JSON 序列化,它將資料封裝成我們所期望的物件格式。然而,需要注意的是,雖然 AI 的提示詞旨在儘量限制其回覆內容,以使其儘可能符合我們的要求,但由於各種因素的影響,我們無法保證其返回的格式會完全按照預設進行。
因此,為了確保程式的穩健性和可靠性,在此過程中引入了異常捕獲機制。這一機制能夠有效地處理潛在的格式不一致或錯誤,從而確保應用在面對不符合預期的資料時,能夠平穩執行而不至於崩潰。
函式回撥
AI目前能夠發揮一定作用,主要得益於模型的函式呼叫功能。如果僅僅依靠訓練模型進行聊天回答,其實際價值是相對有限的,因為這種方式的成本非常高,很多企業難以承受。然而,隨著函式回撥功能的引入,AI可以實時訪問和利用各種資料,包括實時資料和業務資料,使其能夠根據提供的資訊進行更為精準和有效的回答,從而具備了實質性的業務能力。
接下來,我們來看看Spring AI是如何實現這一點的。
基本用法
瞭解了之前的 Spring AI 用法文章後,你大概已經掌握瞭如何建立一個 Function 函式。接下來,我們將直接深入探討如何將這個函式新增到我們的專案中。
@PostMapping("/ai-function")
ChatDataPO functionGenerationByText(@RequestParam("userInput") String userInput) {
String content = this.myChatClientWithSystem.prompt()
.user(userInput)
.functions("CurrentWeather")
.call()
.content();
log.info("content: {}", content);
ChatDataPO chatDataPO = ChatDataPO.builder().code("text").data(ChildData.builder().text(content).build()).build();;
return chatDataPO;
}
在我們的專案中,functions 函式允許新增多種功能,不僅僅侷限於單一工具的呼叫。例如,在視覺化智慧體的應用中,如千帆 AppBuilder,我們可以觀察到思考輪數的運用,這其中涉及了多個工具的呼叫。這種方式為我們的智慧體提供了更豐富的功能和靈活性。
接下來,我們將進行一次函式的呼叫,以實際展示其效果。
在這裡,我們使用了一個固定的 30 度作為示例值,但你可以在函式方法內部透過介面呼叫其他第三方服務來獲取實時資料。因此,透過整合外部資料來源,你可以實現更為智慧和適應性強的功能。
原始碼分析
還記得我們之前討論過的內容嗎?在回答的最後,我們會進行一次判斷,以確定當前的輸出是否為函式呼叫。這一過程是確保系統能夠準確識別和執行函式的重要環節。接下來,我們將展示相關的原始碼,以便更深入地理解這一機制的具體實現:
if (isToolCall(chatResponse,
Set.of(ChatCompletionFinishReason.TOOL_CALLS.name(), ChatCompletionFinishReason.STOP.name()))) {
var toolCallConversation = handleToolCalls(prompt, chatResponse);
// Recursively call the call method with the tool call message
// conversation that contains the call responses.
return this.call(new Prompt(toolCallConversation, prompt.getOptions()));
}
我也在這裡設定了一個斷點,以便大家可以清楚地看到這一過程。這一斷點幫助我們確認,返回的結果完全是由 AI 生成的。在這個基礎上,我們會進行進一步的判斷,以決定是否需要呼叫函式工具。
接下來,我們將進入函式呼叫的過程。這一步驟至關重要,因為函式的返回值將被再次提供給 AI,作為後續回答的參考。我們來看看具體是如何進行函式呼叫的。雖然我已經找到了相關的原始碼,但為了讓大家更容易理解這個過程,我將提供一張視覺化的圖片。這張圖片將清晰地展示函式呼叫的流程,以及返回值是如何被整合進 AI 的回答中的。
傳送這些引數的原因在於,在發起請求時已經設定了相關限制。以下是我擷取下來的請求引數:
tools=[FunctionTool[type=FUNCTION, function=Function[description=獲取指定地點的天氣情況, name=CurrentWeather, parameters={$schema=https://json-schema.org/draft/2020-12/schema, type=object, properties={location={type=string}, unit={type=string, enum=[C, F]}}}]]]
目前幾乎所有第三方AI介面都提供了一個名為 tools
的引數,用於傳遞我們需要的引數。以OpenAI為例:
呼叫函式介面
由於我們的函式實現了 @FunctionalInterface
介面,因此 call
這一行實際上會呼叫我們定義的 apply
介面。鑑於我們的引數是一個實體記錄,系統會對其進行 JSON 轉化和封裝,隨後再進行呼叫。具體過程如下所示:
public String call(String functionArguments) {
// Convert the tool calls JSON arguments into a Java function request object.
I request = fromJson(functionArguments, inputType);
// extend conversation with function response.
return this.andThen(this.responseConverter).apply(request);
}
因此,即使所有操作都已結束,如果在下次 AI 判斷中仍然需要呼叫工具,系統將繼續進行迴圈,直到所有問題都得到完整的回答為止。這種設計確保了整個過程的連貫性和完整性。
總結
在這次探討中,我們深入挖掘了Spring AI框架如何與Java程式完美結合,提升業務能力。隨著AI技術的不斷髮展,其在Java應用中的整合成為了提升開發效率和使用者體驗的關鍵。我們不僅分析了實體類的對映與控制,還探討了函式回撥的強大功能,展示瞭如何透過Spring AI有效處理這些複雜互動。
希望這些分析能夠激發你對AI應用的靈感,並促使你在自己的專案中大膽嘗試,將AI技術融入到業務流程中。期待未來能看到大家的創意實現和應用!
完結撒花!關於Spring AI的一系列原始碼分析到此結束,以後如果還有感興趣的出發點,我也會繼續為大家帶來分析!
我是努力的小雨,一名 Java 服務端碼農,潛心研究著 AI 技術的奧秘。我熱愛技術交流與分享,對開源社群充滿熱情。同時也是一位騰訊雲創作之星、阿里雲專家博主、華為云云享專家、掘金優秀作者。
💡 我將不吝分享我在技術道路上的個人探索與經驗,希望能為你的學習與成長帶來一些啟發與幫助。
🌟 歡迎關注努力的小雨!🌟