利用Spring Boot以及Spring AI構建生成式人工智能應用
Spring AI,作為行業領導者,通過其強大、靈活的API和先進的功能,為各種行業提供了顛覆性的解決方案。在本專題中,我們將深入探討Spring AI在各領域的應用示例,每個案例都將展示Spring AI如何滿足特定需求,實現目標,并將這些LESSONS LEARNED擴展到更廣泛的應用。希望這個專題能對你有所啟發,更深入地理解和利用Spring AI的無限可能。
Spring框架在軟件開發領域已經有超過20年的歷史,自Spring Boot 1.0版本發布以來已有10年。現在,無人會質疑,Spring創造了一種獨特的風格,使開發者從重復任務中解放出來,專注于提供業務價值。隨著時間的推移,Spring的技術深度不斷增強,涵蓋了廣泛的開發領域和技術。另一方面,隨著更多的專用解決方案得到嘗試,概念的驗證被創建,并最終在項目的保護下得到推廣,其技術廣度不斷擴大。
Spring AI 項目就是一個實例,根據其參考文檔,該項目旨在簡化當生成式人工智能層需要被引入應用時的開發過程。開發者再次從重復任務中解放出來,可以直接通過簡單的接口與預先訓練的模型(包含實際處理算法)進行交互。
通過直接或者通過 Spring AI 以編程方式與生成式預訓練的轉換器(GPTs)交互,用戶(開發者)不需要擁有廣泛的機器學習知識。但作為一名工程師,我強烈認為,即使這些開發者工具可以方便快捷地使用并產生結果,我建議我們需要克制自己,警覺觀察,嘗試首先理解基本概念。此外,通過遵循這條路徑,產出的結果可能會更有價值。
目的
本文介紹了如何將 Spring AI 集成到Spring Boot應用,與OpenAI進行編程交互。我們假定prompt設計一般來說是一種最先進的活動。因此,在實驗過程中使用的prompt非常有教導性,但應用性不大。此處的重點是通訊接口,即 Spring AI API。
實施前
首先要明確自身使用GPT解決方案的理由,除了希望提供更好的質量,節省時間和降低成本。
生成式人工智能據說擅長執行大量耗時的任務,速度更快,效率更高,生成結果。此外,如果這些結果進一步經過經驗豐富且智慧的人類驗證,獲得有用結果的可能性會增加。
接下來,應抵制立即跳入實施的誘惑,至少要花一些時間熟悉一下一般概念。對生成式人工智能概念的深入探索遠遠超出了本文的范圍。然而,出現在交互中的“主要角色”在下面簡要概述。
舞臺 - 生成式人工智能是人工智能的一部分
輸入 - 提供的數據(輸入)
輸出 - 計算結果(輸出)
大型語言模型(LLM)- 根據解釋的輸入產生輸出的微調算法
提示詞- 一種最先進的接口,通過它將輸入傳入模型
提示詞模板 - 允許構造結構化參數化提示的組件
令牌 - 算法在內部將輸入轉換為令牌,然后使用這些令牌來編譯結果,并最終從中構造輸出
模型的環境窗口 - 模型限制每次調用的令牌數量的閾值(通常,使用的令牌越多,操作就越昂貴)
最后,可以開始實施,但隨著實施的進行,建議回顧和優化前兩個步驟。
提示詞
在本次練習中,我們請求如下:
Write {count = three} reasons why people in {location = Romania} should consider a {job = software architect} job.
These reasons need to be short, so they fit on a poster.
For instance, "{job} jobs are rewarding."
上面內容代表了提示詞模板。按照建議,應作為提示詞的一部分提供清晰的主題,清晰的任務含義以及額外的有用信息,以提高結果的準確性。
提示詞包含三個參數
count - 希望作為輸出的原因數量
job - 感興趣的領域或工作
location - 工作申請者所在的國家,城鎮,地區等
概念驗證
在這篇文章中,簡單的概念驗證目標如下:
將 Spring AI 集成到Spring Boot應用程序并使用它
允許客戶端通過應用程序與 Open AI 進行通信
客戶端向應用程序發出參數化的HTTP請求
應用程序使用一個提示詞來創建輸入,發送給 Open AI 并獲取輸出
應用程序將響應發送給客戶端
圖片
項目設置
Java 21
Maven 3.9.2
Spring Boot – v. 3.2.2
Spring AI – v. 0.8.0-SNAPSHOT (仍在開發,實驗性)
代碼實現
Spring AI 集成
通常,這是一個基本步驟,不一定值得一提。然而,因為 Spring AI 目前以快照形式發布,為了能夠集成 Open AI 自動配置依賴,你需要添加一個引用到 Spring 快照倉庫。
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
下一步是添加 spring-ai-openai-spring-boot-starter Maven 依賴項。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>0.8.0-SNAPSHOT</version>
</dependency>
Open AI ChatClient 現在是應用程序類路徑的一部分。它是用來向 Open AI 發送輸入并獲取輸出的組件。
為了能夠連接到AI模型,需要在 application.properties 文件中設置 spring.ai.openai.api-key 屬性。
spring.ai.openai.api-key = api-key-value
它的值代表了用戶的有效API密鑰,用戶將通過此密鑰進行通信。通過訪問[資源2],可以注冊或登錄并生成一個。
客戶端 - Spring Boot應用程序通信
概念驗證的第一部分是客戶端應用程序(例如瀏覽器,curl等)與開發的應用程序之間的通信。這是通過一個 REST 控制器實現的,可以通過HTTP GET請求訪問。
URL路徑是 /job-reasons,還有之前在定義提示時概述的三個參數,這將導致如下格式:
/job-reasons?count={count}&job={job}&locatinotallow={location}
和相應的控制器:
@RestController
public class OpenAiController {
@GetMapping("/job-reasons")
public ResponseEntity<String> jobReasons(@RequestParam(value = "count", required = false, defaultValue = "3") int count,
@RequestParam("job") String job,
@RequestParam("location") String location) {
return ResponseEntity.ok().build();
}
}
由于來自 Open AI 的響應將是一個字符串,因此控制器返回一個封裝了字符串的ResponseEntity。如果我們運行應用程序并發出請求,當前響應體部分沒有返回任何內容。
客戶端 - Open AI 通信
Spring AI 目前主要關注處理語言并產生語言或數字的AI模型。在前一類別中, Open AI 模型的例子包括GPT4-openai或GPT3.5-openai。
為了與這些AI模型(實際上是指 Open AI 算法)進行交互, Spring AI 提供了一個統一的接口。
ChatClient接口目前支持文本輸入和輸出,并具有簡單的契約。
@FunctionalInterface
public interface ChatClient extends ModelClient<Prompt, ChatResponse> {
default String call(String message) {
Prompt prompt = new Prompt(new UserMessage(message));
return call(prompt).getResult().getOutput().getContent();
}
ChatResponse call(Prompt prompt);
}
確實如此,功能接口的實際方法通常是被使用的方法。
在我們的概念驗證中,這正是我們所需要的,一種調用 Open AI 并發送目標參數化 Prompt 作為參數的方式。我們定義了以下的OpenAiService,在其中注入了一個 ChatClient 的實例。
@Service
public class OpenAiService {
private final ChatClient client;
public OpenAiService(OpenAiChatClient aiClient) {
this.client = aiClient;
}
public String jobReasons(int count, String domain, String location) {
final String promptText = """
Write {count} reasons why people in {location} should consider a {job} job.
These reasons need to be short, so they fit on a poster.
For instance, "{job} jobs are rewarding."
""";
final PromptTemplate promptTemplate = new PromptTemplate(promptText);
promptTemplate.add("count", count);
promptTemplate.add("job", domain);
promptTemplate.add("location", location);
ChatResponse response = client.call(promptTemplate.create());
return response.getResult().getOutput().getContent();
}
}
如果應用程序正在運行,那么可以從瀏覽器執行以下請求:
http://localhost:8080/gen-ai/job-reasons?count=3&job=software%20architect&locatinotallow=Romania
這下面的結果被檢索出來的結果:
利潤豐裕的職業:軟件架構師的工作提供了有競爭力的薪酬和出色的增長機會,確保在羅馬尼亞的財務穩定和成功。
熱門職業:隨著技術需求的持續增長,軟件架構師在羅馬尼亞和全世界都備受追捧,提供了豐富的就業前景和就業保障。
創造性問題解決:軟件架構師在設計和開發創新軟件解決方案中扮演著至關重要的角色,使他們可以釋放他們的創造力,并對各種行業產生重大影響。
這就是我們所期望的——一個簡易的接口,通過它,可以要求 Open AI GPT模型寫出一些原因,解釋為何在特定地點的特定工作具有吸引力。
調整和觀察
到目前為止,開發的簡單概念驗證主要使用了默認的配置。
ChatClient實例可以通過各種屬性根據所需需要來配置。雖然這超出了本文的范圍,但在這里舉兩個例子。
spring.ai.openai.chat.options.model 指定要使用的AI模型。默認為'gpt-35-turbo',但'gpt-4'和'gpt-4-32k'指定的是最新版本。雖然這些版本都是可用的,但你可能無法使用按使用量付費的計劃來訪問這些版本,但 Open AI 網站上有更多的信息可以幫助你了解如何適應它。
另一個值得一提的屬性是 spring.ai.openai.chat.options.temperature。根據參考文檔,采樣溫度控制了“回應的創新性”。據說,較高的值會讓輸出“更隨機”,而較低的值會“更專注和決定性”。默認值為0.8,如果我們將其降低到0.3,重啟應用并再次使用相同的請求參數詢問,下面的結果將被檢索出來。
有利可圖的職業機會:羅馬尼亞的軟件架構師工作提供有競爭力的薪水和極好的成長前景,對于尋求財務穩定和職業發展的個人來說,這是一個吸引人的職業選擇。
具有挑戰性和智能刺激的工作:作為一名軟件架構師,你將負責設計和實現復雜的軟件系統,解決復雜的技術問題,并與有才華的團隊合作。這個角色提供了持續的學習機會和在尖端技術上工作的機會。
高需求和工作保障:隨著對技術和數字化轉型的依賴增加,各行各業對熟練軟件架構師的需求在上升。選擇在羅馬尼亞的軟件架構師工作確保了工作安全和廣泛的就業選擇,無論是在本地還是國際上。
可以看出,這種情況下的輸出更具描述性。
最后一個考慮因素是與獲取的輸出的結構相關的。擁有將實際接收的有效載荷映射到Java對象(例如,類或記錄)的能力將非常方便。截至目前,表示形式是文本形式,實現也是如此。輸出解析器可能實現這一點,類似于Spring JDBC的映射結構。
在這個概念驗證中,我們使用了一個BeanOutputParser,它允許直接將結果反序列化到Java記錄中,如下所示:
public record JobReasons(String job,
String location,
List<String> reasons) {
}
通過將 {format} 作為提示文本的一部分,并將其作為指示提供給 AI 模型。
OpenAiService 方法變為:
public JobReasons formattedJobReasons(int count, String job, String location) {
final String promptText = """
Write {count} reasons why people in {location} should consider a {job} job.
These reasons need to be short, so they fit on a poster.
For instance, "{job} jobs are rewarding."
{format}
""";
BeanOutputParser<JobReasons> outputParser = new BeanOutputParser<>(JobReasons.class);
final PromptTemplate promptTemplate = new PromptTemplate(promptText);
promptTemplate.add("count", count);
promptTemplate.add("job", job);
promptTemplate.add("location", location);
promptTemplate.add("format", outputParser.getFormat());
promptTemplate.setOutputParser(outputParser);
final Prompt prompt = promptTemplate.create();
ChatResponse response = client.call(prompt);
return outputParser.parse(response.getResult().getOutput().getContent());
}
再次調用時,輸出如下:
{
"job":"software architect",
"location":"Romania",
"reasons":[
"High demand",
"Competitive salary",
"Opportunities for growth"
]
}
格式符合預期,但解釋的原因似乎較少,這意味著需要進行額外的調整以達到更好的可用性。然而,從概念驗證的角度來看,這是可接受的,因為焦點是形式。
結論
提示設計是任務的重要部分 - 提示越清晰,輸入越好,輸出的質量也就越高。
使用 Spring AI 與各種聊天模型進行集成非常簡單 - 本篇文章展示了一個 Open AI 的集成。
然而,對于 Gen AI 或者幾乎任何技術來說,首先至少要熟悉基本概念是非常重要的。然后,嘗試理解如何進行通訊的魔法,最后再開始編寫“生產”代碼。
最后但同樣重要的是,建議進一步探索 Spring AI API,以了解實現并隨著其不斷發展和改進保持最新狀態。