Java智慧之Spring AI:5分鐘打造智慧聊天模型的利器

努力的小雨發表於2024-07-09

前言

儘管Python最近成為了程式語言的首選,但是Java在人工智慧領域的地位同樣不可撼動,得益於強大的Spring框架。隨著人工智慧技術的快速發展,我們正處於一個創新不斷湧現的時代。從智慧語音助手到複雜的自然語言處理系統,人工智慧已經成為了現代生活和工作中不可或缺的一部分。在這樣的背景下,Spring AI 專案迎來了發展的機遇。儘管該專案汲取了Python專案如LangChain和LlamaIndex的靈感,但Spring AI並不是簡單的移植。該專案的初衷在於推進生成式人工智慧應用程式的發展,使其不再侷限於Python開發者。

Spring AI 的核心理念是提供高度抽象化的元件,作為開發AI應用程式的基礎。這些抽象化元件具備多種實現,使得開發者能夠以最少的程式碼改動便捷地交換和最佳化功能模組。

具體而言,Spring AI 提供了支援多種主流模型提供商的功能,包括OpenAI、Microsoft、Amazon、Google和Hugging Face。支援的模型型別涵蓋了從聊天機器人到文字生成、影像處理、語音識別等多個領域。而其跨模型提供商的可移植API設計,不僅支援同步和流式介面,還提供了針對特定模型功能的靈活選項。

此外,Spring AI 還支援將AI模型輸出對映為POJO,以及與主流向量資料庫提供商(如Apache Cassandra、Azure Vector Search、MongoDB Atlas等)無縫整合的能力。其功能不僅侷限於模型本身,還包括了資料工程中的ETL框架和各種便利的函式呼叫,使得開發AI應用程式變得更加高效和可靠。

快速實戰

本期實戰是我們的第一篇,旨在透過快速展示Spring AI專案,讓大家瞭解它的優點和特性。為了方便大家使用,我還將本期的原始碼提交到了倉庫中,並加入了swagger-ui的API呼叫介面,使得使用起來更加便捷。如果你對此感興趣,歡迎前往檢視star。同時,我也會持續維護這個專案,確保它始終保持活躍。

倉庫地址:https://github.com/StudiousXiaoYu/spring-ai-demo

專案生成

當我們開始時,首先需要建立一個專案結構。我們可以前往官方網站,快速生成Spring AI的依賴並建立專案。

image

聊天模型

在大型模型中,聊天模型扮演著至關重要的角色。那麼,SpringAI是如何對其進行封裝的呢?本期主要著重展示如何有效利用Spring AI的ChatClient,特別是在本示例中應用Spring AI的智慧聊天模型。

日誌級別

在這個過程中,如果想要檢視請求的細節日誌,務必將日誌級別調整至DEBUG,具體操作如下:

image

模型配置

當我們使用一個模型時,必須首先在專案中加入相關的依賴,加入依賴後還需要在配置檔案中填寫相應的配置資訊。

image

注入model

那麼模型可以自動注入,我們可以直接使用它。在本期演示中,我們將展示三種自定義模型的注入方式,具體如下:

    private final ChatClient myChatClientWithSystem;

    private final ChatClient myChatClientWithParam;

    /**
     * 可以選擇自動注入、也可以在方法內自定義,此客戶端無系統文字
     */
    private final ChatClient chatClient;

    public MyController(ChatClient.Builder chatClientBuilder, MyChatClientWithSystem myChatClient, MyChatClientWithParam myChatClientWithParam) {
        this.chatClient = chatClientBuilder.build();
        this.myChatClientWithSystem = myChatClient.client();
        this.myChatClientWithParam = myChatClientWithParam.client();
    }

好的,讓我來解釋一下這三種情況:

  1. chatClient:這是預設的自動注入的ChatClient,不需要任何條件。
  2. myChatClientWithParam:這是一個注入系統文字並帶有引數的ChatClient。
  3. myChatClientWithSystem:這是一個注入帶有系統文字的ChatClient。

好的,第一種情況不需要處理,我們只需要透過配置類簡單配置下面兩種ChatClient。

@Configuration
class Config {

    @Bean
    MyChatClientWithSystem myChatClientWithSystem(ChatClient.Builder builder) {
        MyChatClientWithSystem build = MyChatClientWithSystem.builder()
                .client(builder.defaultSystem("你是努力的小雨,一名 Java 服務端碼農,潛心研究著 AI 技術的奧秘。我熱愛技術交流與分享,對開源社群充滿熱情。身兼掘金優秀作者、騰訊雲內容共創官、阿里雲專家博主、華為云云享專家等多重身份。")
                .build()).build();
        return build;
    }

    @Bean
    MyChatClientWithParam myChatClientWithParam(ChatClient.Builder builder) {
        MyChatClientWithParam build = MyChatClientWithParam.builder()
                .client(builder.defaultSystem("你是{user}。")
                        .build()).build();
        return build;
    }
}

簡單文字回答

首先,讓我們先來討論一些簡單的問答。

    @GetMapping("/ai")
    String generationByText(String userInput) {
        return this.chatClient.prompt()
            .user(userInput)
            .call()
            .content();
    }

在這段簡練程式碼中,已經實現了各種封裝和互動,為了更好地演示,我們來展示一下:

image

封裝回答實體物件

大家都知道Java是一種物件導向的程式語言,因此在加入人工智慧技術時,為了滿足業務需求,將物件納入其中是不可或缺的。那麼,如何讓人工智慧的回答能夠被Spring框架自動封裝到物件中呢?讓我們來探討一下:

定義一個物件記錄類:一個記錄類(Record Class)的定義,名為 ActorFilms。用於封裝相關欄位記錄類自動實現了 toString()、equals()、hashCode() 和 getter 方法,使得物件的字串表示、相等性比較和雜湊計算變得簡單。你可以直接使用 actorFilms.toString()、actorFilms.equals(anotherActorFilms) 和 actorFilms.hashCode()。

public record ActorFilms(String actor, List<String> movies) {
}
    @GetMapping("/ai-Entity")
    ActorFilms generationByEntity() {
        ActorFilms actorFilms = chatClient.prompt()
                .user("Generate the filmography for a random actor.")
                .call()
                .entity(ActorFilms.class);
        return actorFilms;
    }

可以看到,只需簡單地將entity設定為ActorFilms。接下來,我們需要檢查返回的物件是否符合預期。

image

當使用者輸入資訊後,系統返回一個實體型別的回答。這種實體型別的回答之所以能夠被封裝,是因為在傳送資訊時,系統不僅僅傳送了使用者輸入的文字,還在其後新增了額外的資訊。
Generate the filmography for a random actor.\r\nYour response should be in JSON format.\r\nDo not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.\r\nDo not include markdown code blocks in your response.\r\nRemove the ```json markdown from the output.\r\nHere is the JSON Schema instance your output must adhere to:\r\n```{\r\n \"$schema\" : \"https://json-schema.org/draft/2020-12/schema\",\r\n \"type\" : \"object\",\r\n \"properties\" : {\r\n \"actor\" : {\r\n \"type\" : \"string\"\r\n },\r\n \"movies\" : {\r\n \"type\" : \"array\",\r\n \"items\" : {\r\n \"type\" : \"string\"\r\n }\r\n }\r\n }\r\n}```\r\n
因此,當後續返回的資料為大型模型時,例如{"actor": "Emily Blunt", "movies": ["Edge of Tomorrow", "A Quiet Place", "The Devil Wears Prada", "Sicario", "Mary Poppins Returns"]},這樣一來Spring就可以幫我將其自動封裝起來了。

封裝回答列表實體物件

當我們需要返回一個列表而不是一個物件時,可以輕鬆地利用Spring AI的封裝功能來實現。讓我們來看看如何操作:

    @GetMapping("/ai-EntityList")
    List<ActorFilms> generationByEntityList() {
        List<ActorFilms> actorFilms = chatClient.prompt()
                .user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
                .call()
                .entity(new ParameterizedTypeReference<List<ActorFilms>>() {
                });
        return actorFilms;
    }

直接使用ParameterizedTypeReference物件即可。為了讓Spring能夠自動封裝返回結果,傳送資訊時也包含了返回格式資訊作為提示。現在我們來檢視演示的結果。

image

流式回答

在前面展示的示例中,大型模型一次性完成回答並將其全部輸出給使用者。然而,前端無法實現打字機效果,因此我們決定採用流式回答的方式來進行演示。

   @GetMapping("/ai-streamWithParam")
    Flux<String> generationByStreamWithParam() {
        var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<List<ActorFilms>>() {
        });

        Flux<String> flux = this.chatClient.prompt()
                .user(u -> u.text("""
                            Generate the filmography for a random actor.
                            {format}
                          """)
                        .param("format", converter.getFormat()))
                .stream()
                .content();

        String content = flux.collectList().block().stream().collect(Collectors.joining());

        List<ActorFilms> actorFilms = converter.convert(content);
        log.info("actorFilms: {}", actorFilms);
        return flux;
    }

為了演示使用者資訊中的引數傳遞,我對流式回答進行了一個阻塞操作。如果不需要的話,可以將其刪除。另外,由於我需要封裝一個列表物件,所以進行了阻塞操作。實際上,這與上面提到的一樣,即在問答中直接定義了大模型返回的格式。好的,我們來看一下返回結果。

image

帶有系統資訊的client

這次我們將演示客戶端的配置。在對話中,我們知道有三種身份標識:system、user、assistant。至今,我們尚未展示系統身份標識,但之前我們已經定義了系統形式的客戶端。因此,這次我們將直接使用它:

    @GetMapping("/ai-withSystemClient")
    Map<String, String> generationByTextWithSystemClient(String message) {
        return Map.of("completion", myChatClientWithSystem.prompt().user(message).call().content());
    }

這段程式碼非常簡單,只需使用ChatClient即可。使用者輸入後,會返回一個Map型別的回答,其中key為"completion",對應的value為回答內容。讓我們一起來看一下結果吧。

image

可以看出,實際上他已經將我的system資訊包含在內了。

帶有引數資訊的client

當您需要演示帶有引數的情況時,您可以考慮以下方法:在使用者輸入後,返回一個Map型別的回答,其中包含鍵值對,鍵為"completion",值為相應的回答。在實際業務場景中,引數是不可避免的,因此這種演示方式可以更好地展示人工智慧的適用性。讓我們繼續探討這一點:

    @GetMapping("/ai-withParamClient")
    Map<String, String> generationByTextWithParamClient(String message, String user) {
        return Map.of("completion", myChatClientWithParam.prompt().system(sp ->sp.param("user",user)).user(message).call().content());
    }

這裡也是很簡單的一句話,所以我們看下效果:

image

如果您對回答感到困惑,我們可以檢視後臺傳輸日誌,以瞭解傳輸的引數詳情。

image

可以注意到,實際上我們已經成功將引數設定完成。

聊天曆史

在最後一個主要的業務場景中,每個人都會有自己的聊天記錄。我們不能一直進行無狀態的對話,這樣會顯得很不智慧。因此,必須要有聊天記錄的功能。雖然Spring AI尚未完全確定如何封裝這部分功能,但已經提供了一個簡單的物件類供我們呼叫。讓我們來看一下:

    @GetMapping("/ai-chatMemory")
    String generationByChatMemory(HttpServletRequest request, String userInput) {
        String sessionId = request.getSession().getId();
        chatMemory.add(sessionId, new UserMessage(userInput));
        String content = this.chatClient.prompt()
                .advisors(new MessageChatMemoryAdvisor(chatMemory))
                .user(userInput)
                .call()
                .content();
        chatMemory.add(sessionId, new AssistantMessage(content));
        return content;
    }

實際上,在這種情況下,我們需要自行建立並維護一個聊天曆史物件。因此,每次進行聊天前和聊天后,我們都應該將所需的資訊新增到該物件中,然後直接使用它。讓我們來看一下這種做法的效果:

image

image

可以看到,實際上在這裡已經將歷史記錄一併呈現了出來。

總結

透過本文的介紹,我們深入瞭解了Spring AI專案的優勢和特性,以及在實際應用中的快速實戰示例。Spring AI作為一個高度抽象化的人工智慧應用程式開發框架,為開發者提供了便捷的模型支援、靈活的功能模組交換和最佳化能力。它不僅能將AI模型輸出對映為POJO,還能與主流向量資料庫提供商無縫整合,從而顯著提升開發AI應用程式的效率和可靠性。

與Python相比,Java在企業級應用和大型系統中具有顯著優勢。Java語言的靜態型別和嚴格的編譯時檢查使得程式碼更加健壯和易於維護,尤其適合需要高度可靠性和長期支援的專案。同時,Java生態系統的成熟度和廣泛應用確保了開發者可以輕鬆找到豐富的庫和工具支援,加速開發週期並降低專案風險。

希望本文能為您對Spring AI專案的理解和應用提供幫助,同時也歡迎您關注和使用這個專案,持續關注更新和維護。讓我們一起見證人工智慧技術的不斷進步和應用!


我是努力的小雨,一名 Java 服務端碼農,潛心研究著 AI 技術的奧秘。我熱愛技術交流與分享,對開源社群充滿熱情。身兼掘金優秀作者、騰訊雲內容共創官、阿里雲專家博主、華為云云享專家等多重身份。

🚀 目前,我的探索重點在於 AI Agent 智慧體應用,我對其充滿好奇,並不斷探索著其潛力與可能性。如果你也對此領域充滿熱情,歡迎與我交流分享,讓我們共同探索未知的領域!

💡 我將不吝分享我在技術道路上的個人探索與經驗,希望能為你的學習與成長帶來一些啟發與幫助。

🌟 歡迎關注努力的小雨!🌟

相關文章