使用 Embabel Agent Framework 在 Java 中建立 AI 代理
1.概述
現代應用程式越來越多地使用大型語言模型 (LLM) 來建立超越簡單問答的解決方案。為了實現大多數實際用例,我們需要一個能夠在 LLM 和外部工具之間協調複雜工作流程的 AI 代理程式。
Embabel Agent Framework由 Spring Framework 創始人Rod Johnson創建,旨在透過在 Spring AI 之上提供更高層級的抽象化來簡化在 JVM 上建立 AI 代理程式。
透過使用目標導向行動計畫 (GOAP) ,它使代理人能夠動態地找到實現目標的路徑,而無需明確地編寫每個工作流程。
在本教程中,我們將透過建立一個名為 Quizzard 的基本測驗生成代理來探索 Embabel 代理框架。我們的代理商從部落格文章 URL 取得內容,然後使用它來產生多項選擇題。
2. 設定項目
在開始實作 Quizzard 代理之前,我們需要包含必要的依賴項並正確配置我們的應用程式。
2.1. 依賴項
讓我們先為專案的pom.xml
檔案加入必要的依賴項:
<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-starter</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
對於我們的 Spring Boot 應用程序,我們導入embabel-agent-starter
依賴項,它為我們提供了建構 Quizzard 代理程式所需的所有核心類別。
由於我們使用的是庫的快照版本,因此我們還需要將 Embabel 快照儲存庫新增到我們的pom.xml
中:
<repositories>
<repository>
<id>embabel-snapshots</id>
<url>https://repo.embabel.com/artifactory/libs-snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
在這裡,我們可以存取 Embabel 的快照工件,而不是標準的 Maven Central 儲存庫。
2.2. 配置 LLM 模型
接下來,讓我們在application.yaml
檔案中設定一個聊天模型:
embabel:
models:
default-llm: claude-opus-4-20250514
為了演示,我們指定 Anthropic 的Claude 4 Opus作為所有代理操作的預設模型,使用claude-opus-4-20250514
模型 ID。
**值得注意的是,Embabel 支援多個 LLM 同時使用**,透過這種方式我們可以實現成本效益,並根據任務的複雜性為特定任務選擇最佳的 LLM。
此外,在執行應用程式時,我們必須在ANTHROPIC_API_KEY
環境變數中傳遞我們的Anthropic API 金鑰。
2.3. 定義測驗產生提示模板
最後,為了確保我們的 LLM 針對部落格內容產生高品質的測驗,我們將定義一個詳細的提示範本。
讓我們在src/main/resources/prompt-templates
目錄中建立一個quiz-generation.txt
檔案:
Generate multiple choice questions based on the following blog content:
Blog title: %s
Blog content: %s
Requirements:
- Create exactly 5 questions
- Each question must have exactly 4 options
- Each question must have only one correct answer
- The difficulty level of the questions should be intermediate
- Questions should test understanding of key concepts from the blog
- Make the incorrect options plausible but clearly wrong
- Questions should be clear and unambiguous
這裡,我們清楚地概述了測驗的要求。我們在提示範本中分別保留了部落格標題和部落格內容的兩個%s
佔位符。我們將用代理類別中的實際值替換它們。
為了簡單起見,我們直接在提示範本中硬編碼了問題數量、選項和難度等級。然而,在實際應用中,我們可以透過屬性來配置這些內容,或根據需求將其作為使用者輸入。
3.創建我們的代理
在 Embabel 中,代理程式是核心元件,它封裝了一組稱為動作的功能,並使用它們來實現目標。
現在我們已經完成了配置,接下來我們來建立 Quizzard 代理程式。首先,我們將定義一個操作,使用模型上下文協定 (MCP) 伺服器從 Web 取得部落格內容;然後,我們將定義另一個操作,使用我們配置的 LLM 產生測驗。
3.1. 使用網頁搜尋取得部落格內容
**為了從部落格中提取內容,我們將使用Fetch MCP 伺服器**。它提供了一個工具,可以從 URL 取得內容並將其轉換為 Markdown 格式。或者,我們也可以使用Brave Search MCP 伺服器。
為了演示,我們將使用 MCP 工具包將此 MCP 伺服器新增至 Docker Desktop。該工具包在 4.42 或更高版本中可用。如果使用的是舊版本,我們可以將 MCP 工具包新增為 Docker 擴充功能。
要使用此 MCP 伺服器,我們首先將應用程式設定為 MCP 用戶端:
@SpringBootApplication
@EnableAgents(mcpServers = {
McpServers.DOCKER_DESKTOP
})
class Application {
// ...
}
使用@EnableAgents
註釋,我們的應用程式將能夠充當MCP客戶端並連接到透過Docker Desktop整合提供的工具。
接下來,讓我們定義我們的代理及其第一個動作:
`@Agent(
name = "quizzard",
description = "Generate multiple choice quizzes from documents"
)
class QuizGeneratorAgent {
@Action(toolGroups = CoreToolGroups.WEB)
Blog fetchBlogContent(UserInput userInput) {
return PromptRunner
.usingLlm()
.createObject(
"Fetch the blog content from the URL given in the following request: '%s'".formatted(userInput),
Blog.class
);
}
}`
在這裡,我們用@Agent
註釋註釋我們的QuizGeneratorAgent
類別以將其聲明為代理。
我們在description
屬性中提供了它的用途,因為它可以幫助 Embabel 選擇正確的代理來處理使用者的請求,尤其是當我們在應用程式中定義多個代理程式時。
接下來,我們定義fetchBlogContent()
方法,並用@Action
註解標記它,表示它是代理可以執行的功能。此外,為了授予我們的操作訪問我們啟用的 fetch MCP 伺服器的權限,我們在toolGroups
屬性中指定了CoreToolGroups.WEB
。
在我們的方法內部,我們使用PromptRunner
類別傳遞一個提示,用於從使用者輸入中提取 URL 的內容。為了將擷取的資訊填入Blog
物件中,我們將Blog
類別作為createObject()
方法的第二個參數傳遞。 Embabel 會自動將指令加入我們的提示中,以便 LLM 產生結構化的輸出。
值得注意的是,Embabel 提供了UserInput
和Blog
領域模型,因此我們無需自行建立。 UserInput 包含使用者的文字請求和UserInput
,而Blog
類型包含部落格標題、內容、作者等欄位。
3.2. 從獲取的部落格產生測驗
現在我們已經有一個能夠獲取部落格內容的操作,下一步是定義一個可以產生測驗的操作。
首先,讓我們定義一筆記錄來表示我們的測驗的結構:
record Quiz(List<QuizQuestion> questions) {
record QuizQuestion(
String question,
List<String> options,
String correctAnswer
) {
}
}
我們的Quiz
記錄包含巢狀的QuizQuestion
記錄列表,其中包含question
,每個問題都有其options
和正確答案。
最後,讓我們為代理商添加第二個動作,以從獲取的部落格內容中產生測驗:
@Value("classpath:prompt-templates/quiz-generation.txt")
private Resource promptTemplate;
@Action
@AchievesGoal(description = "Quiz has been generated")
Quiz generateQuiz(Blog blog) {
String prompt = promptTemplate
.getContentAsString(Charset.defaultCharset())
.formatted(
blog.getTitle(),
blog.getContent()
);
return PromptRunner
.usingLlm()
.createObject(
prompt,
Quiz.class
);
}
我們建立一個新的generateQuiz()
方法,該方法將上一個操作傳回的Blog
物件作為參數。
除了@Action
之外,我們還用@AchievesGoal
註解來註解此方法,以表明此操作的成功執行實現了代理的主要目的。
在我們的方法中,我們將promptTemplate
中的佔位符替換為部落格的標題和內容。然後,我們再次使用PromptRunner
類別根據此prompt
產生一個Quiz
物件。
4. 與我們的代理商互動
現在我們已經建立了代理,讓我們與它進行互動並進行測試。
首先,讓我們在應用程式中啟用互動式 shell 模式:
@EnableAgentShell
@SpringBootApplication
@EnableAgents(mcpServers = {
McpServers.DOCKER_DESKTOP
})
class Application {
// ...
}
我們使用**@EnableAgentShell
註解來註解我們的主要 Spring Boot 類,它為我們提供了一個互動式 CLI 來與我們的代理程式進行互動**。
或者,我們也可以使用@EnableAgentMcpServer
註解將我們的應用程式作為 MCP 伺服器運行,Embabel 會將我們的代理程式暴露為相容 MCP 的工具,供 MCP 用戶端使用。但為了示範方便,我們將盡量簡化。
讓我們啟動我們的應用程式並給我們的代理一個命令:
execute 'Generate quiz for this article: https://www.baeldung.com/spring-ai-model-context-protocol-mcp'
在這裡,我們使用execute
命令向我們的 Quizzard 代理程式發送請求。我們提供自然語言指令,其中包含我們要為其建立測驗的 Baeldung 文章的 URL。
我們來看看執行該命令時產生的日誌:
`[main] INFO Embabel - formulated plan
com.baeldung.quizzard.QuizGeneratorAgent.fetchBlogContent ->
com.baeldung.quizzard.QuizGeneratorAgent.generateQuiz
[main] INFO Embabel - executing action com.baeldung.quizzard.QuizGeneratorAgent.fetchBlogContent
[main] INFO Embabel - (fetchBlogContent) calling tool embabel_docker_mcp_fetch({"url":"https://www.baeldung.com/spring-ai-model-context-protocol-mcp"})
[main] INFO Embabel - received LLM response com.baeldung.quizzard.QuizGeneratorAgent.fetchBlogContent-com.embabel.agent.domain.library.Blog of type Blog from DefaultModelSelectionCriteria in 11 seconds
[main] INFO Embabel - executing action com.baeldung.quizzard.QuizGeneratorAgent.generateQuiz
[main] INFO Embabel - received LLM response com.baeldung.quizzard.QuizGeneratorAgent.generateQuiz-com.baeldung.quizzard.Quiz of type Quiz from DefaultModelSelectionCriteria in 5 seconds
[main] INFO Embabel - goal com.baeldung.quizzard.QuizGeneratorAgent.generateQuiz achieved in PT16.332321S
You asked: UserInput(content=Generate quiz for this article: https://www.baeldung.com/spring-ai-model-context-protocol-mcp, timestamp=2025-07-09T15:36:20.056402Z)
{
"questions":[
{
"question":"What is the primary purpose of the Model Context Protocol (MCP) introduced by Anthropic?",
"options":[
"To provide a standardized way to enhance AI model responses by connecting to external data sources",
"To replace existing LLM architectures with a new protocol",
"To create a new programming language for AI development",
"To establish a security protocol for AI model deployment"
],
"correctAnswer":"To provide a standardized way to enhance AI model responses by connecting to external data sources"
},
{
"question":"In the MCP architecture, what is the relationship between MCP Clients and MCP Servers?",
"options":[
"MCP Clients establish many-to-many connections with MCP Servers",
"MCP Clients establish 1:1 connections with MCP Servers",
"MCP Servers establish connections to MCP Clients",
"MCP Clients and Servers communicate through a central message broker"
],
"correctAnswer":"MCP Clients establish 1:1 connections with MCP Servers"
},
{
"question":"Which transport mechanism is used for TypeScript-based MCP servers in the tutorial?",
"options":[
"HTTP transport",
"WebSocket transport",
"stdio transport",
"gRPC transport"
],
"correctAnswer":"stdio transport"
},
{
"question":"What annotation is used to expose custom tools in the MCP server implementation?",
"options":[
"@Tool",
"@MCPTool",
"@Function",
"@Method"
],
"correctAnswer":"@Tool"
},
{
"question":"What type of transport is used for custom MCP servers in the tutorial?",
"options":[
"stdio transport",
"HTTP transport",
"SSE transport",
"TCP transport"
],
"correctAnswer":"SSE transport"
}
]
}
LLMs used: [claude-opus-4-20250514]
Prompt tokens: 3,053, completion tokens: 996
Cost: $0.0141
Tool usage:
ToolStats(name=embabel_docker_mcp_fetch, calls=1, avgResponseTime=1657 ms, failures=0)`
日誌提供了代理執行流程的清晰視圖。
首先,我們可以看到 Embabel 自動且正確地制定了計劃來實現我們定義的目標。
接下來,代理執行計劃,從fetchBlogContent
操作開始。此外,我們可以看到它使用我們提供的 URL 呼叫了 fetch MCP 伺服器。取得內容後,代理程式繼續執行generateQuiz
操作。
最後,代理確認其已實現目標並以 JSON 格式列印最終測驗,以及令牌使用情況和成本等有用指標。
5. 結論
在本文中,我們探討如何使用 Embabel 代理框架來建立智慧代理。
我們建立了一個實用的多步驟代理 Quizzard,它可以取得網頁內容並產生測驗。我們的代理人能夠動態確定實現目標所需的操作順序。
與往常一樣,本文中使用的所有程式碼範例均可在 GitHub 上找到。