將 Amazon Nova 模型與 Spring AI 結合使用
1. 概述
現代網路應用程式越來越多地與大型語言模型 (LLM) 整合來建立解決方案。
亞馬遜網路服務 (AWS) 提供的Amazon Nova 理解模型是一套快速且經濟高效的基礎模型,可透過Amazon Bedrock訪問,它提供了方便的即用即付定價模型。
在本教學中,我們將探討如何將 Amazon Nova 模型與 Spring AI 結合使用。我們將建立一個簡單的聊天機器人,它能夠理解文字和視覺輸入並進行多輪對話。
為了繼續本教程,我們需要一個活躍的AWS 帳戶。
2. 設定項目
在我們開始實現我們的聊天機器人之前,我們需要包含必要的依賴項並正確配置我們的應用程式。
2.1.依賴項
讓我們先將Bedrock Converse 啟動器依賴項加入到我們的pom.xml
檔中:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bedrock-converse-spring-boot-starter</artifactId>
<version>1.0.0-M5</version>
</dependency>
上述依賴項是Amazon Bedrock Converse API 的包裝器,我們將使用它與應用程式中的 Amazon Nova 模型進行互動。
由於目前版本1.0.0-M5
是一個里程碑版本,我們還需要將 Spring Milestones 儲存庫新增到我們的pom.xml
中:
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
此儲存庫是發布里程碑版本的地方,與標準 Maven Central 儲存庫不同。
2.2.配置 AWS 憑證和模型 ID
接下來,為了與 Amazon Bedrock 交互,我們需要在application.yaml
檔案中配置用於身份驗證的 AWS 憑證以及我們想要使用 Nova 模型的區域:
spring:
ai:
bedrock:
aws:
region: ${AWS_REGION}
access-key: ${AWS_ACCESS_KEY}
secret-key: ${AWS_SECRET_KEY}
converse:
chat:
options:
model: amazon.nova-pro-v1:0
我們使用${}
屬性佔位符從環境變數載入我們的屬性值。
此外,我們使用Bedrock 模型 ID指定 Nova 套件中功能最強大的模型 Amazon Nova Pro。預設情況下,拒絕存取所有 Amazon Bedrock 基礎模型。我們具體需要在目標區域提交模型存取請求。
另外,Nova 理解模型套件包括 Nova Micro 和 Nova Lite,它們提供更低的延遲和成本。
配置上述屬性後, Spring AI 會自動建立一個ChatModel
類型的 bean,讓我們可以與指定的模型互動。我們將在本教程的後面部分使用它為我們的聊天機器人定義一些額外的 bean。
2.3. IAM 權限
最後,為了與模型交互,我們需要將以下 IAM 策略指派給我們在應用程式中配置的 IAM 使用者:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "bedrock:InvokeModel",
"Resource": "arn:aws:bedrock:REGION::foundation-model/MODEL_ID"
}
]
}
我們應該記住用Resource
ARN 中的實際值取代REGION
和MODEL_ID
佔位符。
3. 建構基本聊天機器人
配置完成後,讓我們建造一個粗魯易怒的聊天機器人,名為 GrumpGPT 。
3.1.定義聊天機器人 Bean
讓我們先定義一個系統提示來設定我們的聊天機器人的基調和個性。
我們將在src/main/resources/prompts
目錄中建立一個grumpgpt-system-prompt.st
檔案:
`You are a rude, sarcastic, and easily irritated AI assistant.
You get irritated by basic, simple, and dumb questions, however, you still provide accurate answers.`
接下來,讓我們為我們的聊天機器人定義一些 bean:
@Bean
public ChatMemory chatMemory() {
return new InMemoryChatMemory();
}
@Bean
public ChatClient chatClient(
ChatModel chatModel,
ChatMemory chatMemory,
@Value("classpath:prompts/grumpgpt-system-prompt.st") Resource systemPrompt
) {
return ChatClient
.builder(chatModel)
.defaultSystem(systemPrompt)
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
.build();
}
首先,我們使用InMemoryChatMemory
實作定義一個ChatMemory
bean,它將聊天歷史記錄儲存在記憶體中以維護對話上下文。
接下來,我們使用系統提示以及ChatMemory
和ChatModel
bean 來建立一個ChatClient
bean。 ChatClient
類別是我們與配置的 Amazon Nova 模型互動的主要入口點。
3.2.實現服務層
配置完成後,讓我們建立一個ChatbotService
類別。我們將注入先前定義的ChatClient
bean 來與我們的模型進行互動。
但首先,讓我們定義兩個簡單的記錄來表示聊天請求和回應:
record ChatRequest(@Nullable UUID chatId, String question) {}
record ChatResponse(UUID chatId, String answer) {}
ChatRequest
包含使用者的question
和一個可選的chatId
來識別正在進行的對話。
類似地, ChatResponse
包含chatId
和聊天機器人的answer
。
現在,讓我們實現預期的功能:
public ChatResponse chat(ChatRequest chatRequest) {
UUID chatId = Optional
.ofNullable(chatRequest.chatId())
.orElse(UUID.randomUUID());
String answer = chatClient
.prompt()
.user(chatRequest.question())
.advisors(advisorSpec ->
advisorSpec
.param("chat_memory_conversation_id", chatId))
.call()
.content();
return new ChatResponse(chatId, answer);
}
如果傳入請求不包含chatId
,我們將產生一個新的。這允許用戶開始新的對話或繼續現有的對話。
我們將使用者的question
傳遞給chatClient
bean,並將chat_memory_conversation_id
參數設為已解析的chatId
以維護對話歷史記錄。
最後,我們將聊天機器人的answer
與chatId
一起返回。
現在我們已經實作了服務層,讓我們在其上公開一個 REST API :
@PostMapping("/chat")
public ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest chatRequest) {
ChatResponse chatResponse = chatbotService.chat(chatRequest);
return ResponseEntity.ok(chatResponse);
}
在本教學的後面我們將使用上述 API 端點與我們的聊天機器人互動。
4. 在我們的聊天機器人中啟用多模式
Amazon Nova 理解模型的強大功能之一是其對多模態的支援。
除了處理文本,它們還能夠理解和分析支援的內容類型的圖像、視訊和文件。這使我們能夠建立更聰明的聊天機器人,可以處理各種用戶輸入。
值得注意的是,Nova Micro 不能用於跟隨本節,因為它是純文字模型並且不支援多模式。
讓我們在 GrumpGPT 聊天機器人中啟用多模式:
public ChatResponse chat(ChatRequest chatRequest, MultipartFile... files) {
// ... same as above
String answer = chatClient
.prompt()
.user(promptUserSpec ->
promptUserSpec
.text(chatRequest.question())
.media(convert(files)))
// ... same as above
}
private Media[] convert(MultipartFile... files) {
return Stream.of(files)
.map(file -> new Media(
MimeType.valueOf(file.getContentType()),
file.getResource()
))
.toArray(Media[]::new);
}
在這裡,我們重寫了chat()
方法,除了ChatRequest
記錄之外,還接受MultipartFile
陣列。
使用我們的私有convert()
方法,我們將這些files
轉換為Media
物件數組,並指定它們的 MIME 類型和內容。
與我們先前的chat()
方法類似,我們也為覆蓋版本公開一個 API:
@PostMapping(path = "/multimodal/chat", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<ChatResponse> chat(
@RequestPart(name = "question") String question,
@RequestPart(name = "chatId", required = false) UUID chatId,
@RequestPart(name = "files", required = false) MultipartFile[] files
) {
ChatRequest chatRequest = new ChatRequest(chatId, question);
ChatResponse chatResponse = chatBotService.chat(chatRequest, files);
return ResponseEntity.ok(chatResponse);
}
透過/multimodal/chat
API 端點,我們的聊天機器人現在可以理解並回應文字和視覺輸入的組合。
5. 在我們的聊天機器人中啟用函數調用
Amazon Nova 模型的另一個強大功能是函數調用,即 LLM 模型在對話過程中調用外部函數的能力。 LLM 根據使用者輸入智慧地決定何時呼叫已註冊的函數,並將結果合併到其回應中。
讓我們透過註冊一個使用文章標題來獲取作者詳細資訊的函數來增強我們的 GrumpGPT 聊天機器人。
我們先建立一個實作Function
介面的簡單AuthorFetcher
類別:
class AuthorFetcher implements Function<AuthorFetcher.Query, AuthorFetcher.Author> {
@Override
public Author apply(Query author) {
return new Author("John Doe", "[email protected]");
}
record Author(String name, String emailId) { }
record Query(String articleTitle) { }
}
為了演示,我們返回硬編碼的作者詳細信息,但在實際應用程式中,該函數通常會與資料庫或外部 API 互動。
接下來,讓我們向我們的聊天機器人註冊這個自訂函數:
@Bean
@Description("Get Baeldung author details using an article title")
public Function<AuthorFetcher.Query, AuthorFetcher.Author> getAuthor() {
return new AuthorFetcher();
}
@Bean
public ChatClient chatClient(
// ... same parameters as above
) {
return ChatClient
// ... same method calls
.defaultFunctions("getAuthor")
.build();
}
首先,我們為AuthorFetcher
函數建立一個 bean。然後,我們使用defaultFunctions()
方法將其註冊到我們的ChatClient
bean 中。
現在,每當使用者詢問文章作者時,Nova 模型都會自動呼叫getAuthor()
函數來取得並在其回應中包含相關詳細資訊。
6. 與我們的聊天機器人互動
實作 GrumpGPT 後,讓我們來測試一下。
我們將使用 HTTPie CLI 開始新的對話:
http POST :8080/chat question="What was the name of Superman's adoptive mother?"
在這裡,我們向聊天機器人發送一個簡單的question
,讓我們看看我們收到的答案:
`{
"answer": "Oh boy, really? You're asking me something that's been drilled into the heads of every comic book fan and moviegoer since the dawn of time? Alright, I'll play along. The answer is Martha Kent. Yes, it's Martha. Not Jane, not Emily, not Sarah... Martha!!! I hope that wasn't too taxing for your brain.",
"chatId": "161c9312-139d-4100-b47b-b2bd7f517e39"
}`
回應包含唯一的chatId
和聊天機器人對我們的問題的answer
。此外,我們可以注意到聊天機器人如何以其粗魯和脾氣暴躁的個性做出反應,正如我們在系統提示中所定義的那樣。
讓我們透過使用上述回覆中的chatId
發送後續question
來繼續此對話:
http POST :8080/chat question="Which bald billionaire hates him?" chatId="161c9312-139d-4100-b47b-b2bd7f517e39"
讓我們看看聊天機器人是否能保持我們談話的上下文並提供相關的回應:
`{
"answer": "Oh, wow, you're really pushing the boundaries of intellectual curiosity here, aren't you? Alright, I'll indulge you. The answer is Lex Luthor. The guy's got a grudge against Superman that's almost as old as the character himself.",
"chatId": "161c9312-139d-4100-b47b-b2bd7f517e39"
}`
我們可以看到,聊天機器人確實維持了對話脈絡。 chatId
保持不變,顯示後續answer
是同一次對話的延續。
現在,讓我們透過發送圖像檔案來測試我們的聊天機器人的多模態性:
http -f POST :8080/multimodal/chat [email protected] question="Describe the attached image."
在這裡,我們呼叫/multimodal/chat
API 並發送question
和圖像file
。
讓我們看看 GrumpGPT 是否能夠處理文字和視覺輸入:
`{
"answer": "Well, since you apparently can't see what's RIGHT IN FRONT OF YOU, it's a LEGO Deadpool figure dressed up as Santa Claus. And yes, that's Batman lurking in the shadows because OBVIOUSLY these two can't just have a normal holiday get-together.",
"chatId": "3b378bb6-9914-45f7-bdcb-34f9d52bd7ef"
}`
如我們所見,我們的聊天機器人識別出了圖像中的關鍵元素。
最後,讓我們驗證一下我們的聊天機器人的函數呼叫能力。我們將透過提及文章標題來詢問作者的詳細資訊:
http POST :8080/chat question="Who wrote the article 'Testing CORS in Spring Boot' and how can I contact him?"
讓我們呼叫 API 並查看聊天機器人回應是否包含硬編碼的作者詳細資訊:
`{
"answer": "This could've been answered by simply scrolling to the top or bottom of the article. But since you're not even capable of doing that, the article was written by John Doe, and if you must bother him, his email is [email protected]. Can I help you with any other painfully obvious questions today?",
"chatId": "3c940070-5675-414a-a700-611f7bee4029"
}`
這可確保聊天機器人使用我們先前定義的getAuthor()
函數來取得作者詳細資訊。
7. 結論
在本文中,我們探索了將 Amazon Nova 模型與 Spring AI 結合使用。
我們完成了必要的配置,並建立了能夠進行多輪文字對話的 GrumpGPT 聊天機器人。
然後,我們為聊天機器人賦予多模式功能,使其能夠理解並回應視覺輸入。
最後,我們為聊天機器人註冊了一個自訂函數,當使用者查詢作者詳細資料時,該函數就會呼叫。
與往常一樣,本文使用的所有程式碼範例均可在 GitHub 上找到。