1. 概述
現代應用程序越來越多地使用大型語言模型 (LLM) 構建超越簡單問答的解決方案。為了實現大多數現實世界的用例,我們需要一個能夠協調 LLM 與外部工具之間的複雜工作流程的 AI 代理。
Spring 框架創始人 Rod Johnson 創建的 Embabel Agent Framework,旨在通過在 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 密鑰傳遞到 ANTHROPIC_API_KEY 環境變量中。
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這裏,我們清晰地闡述了測驗的要求。我們留出了兩個百分比佔位符,分別用於博客標題和博客內容。我們將用實際值替換它們,在我們的代理類中。
為了簡化起見,我們直接在提示模板中硬編碼了問題的數量、選項和難度級別。但是,在生產應用程序中,我們可以通過屬性進行配置或根據要求接受它們作為用户輸入。
3. 創建我們的代理
在 Embabel 中,代理是核心組件,它封裝了一組能力,稱為動作,並利用它們來實現目標。
現在我們已經完成了配置,讓我們來構建我們的 Quizzard 代理。首先,我們將定義一個動作,該動作使用 Model Context Protocol (MCP) 服務器從 Web 上獲取博客內容,然後定義另一個動作,該動作使用我們配置的 LLM 從中生成測驗。
3.1. 使用Web搜索獲取博客內容
要從博客中提取內容,我們將使用 Fetch MCP服務器。它提供了一個工具,可以從URL中檢索內容並將其轉換為Markdown。 另一種選擇是使用 Brave Search MCP服務器。
為了演示,我們將使用MCP工具包通過Docker Desktop向該MCP服務器添加,該工具包版本為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
);
}
}在這裏,我們使用 QuizGeneratorAgent 類並添加 @Agent 註解,聲明它是一個代理。
description 屬性提供其目的,因為它有助於 Embabel 選擇正確的代理來處理用户的請求,尤其當我們定義應用程序中的多個代理時。
接下來,我們定義fetchBlogContent() 方法並使用@Action 註解對其進行標註,這標誌着該方法是代理可以執行的能力。 此外,為了授予我們的操作訪問 fetch MCP 服務器的權限,我們在toolGroups 屬性中指定了 CoreToolGroups.WEB。
在我們的方法內部,我們使用PromptRunner 類將提示傳遞到從用户輸入中提取 URL 內容。 為了使用提取的信息填充Blog 對象,我們將Blog 類作為createObject() 方法的第二個參數傳遞。 Embabel 會自動為 LLM 的提示添加説明,以生成 結構化輸出。
值得注意的是,Embabel 提供UserInput 和 Blog 領域模型,因此我們無需自己創建它們。 UserInput 包含用户的文本請求和時間戳,而Blog 類型包括博客標題、內容、作者等字段。
3.2. 從檢索到的博客生成測驗
現在我們已經擁有一個能夠檢索博客內容的操作,下一步是定義一個生成測驗的操作。
首先,讓我們定義一個記錄來表示測驗的結構:
record Quiz(List<QuizQuestion> questions) {
record QuizQuestion(
String question,
List<String> options,
String correctAnswer
) {
}
}我們的 測驗記錄包含一個嵌套的 測驗問題記錄列表,其中包含每個 問題及其 選項和正確答案。
最後,讓我們為我們的代理添加第二個操作,從獲取的博客內容中生成測驗。
@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 {
// ...
}我們為主要的 Spring Boot 類添加了 @EnableAgentShell 註解,該註解為我們提供了一個交互式的 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 格式打印最終測驗,以及諸如 token 使用量和成本等有用的指標。
5. 結論
在本文中,我們探討了如何使用 Embabel 代理框架構建智能代理。
我們構建了一個實用的、多步驟代理,名為 Quizzard,它可以從網頁上抓取內容並根據這些內容生成測驗。該代理能夠動態確定實現其目標的所需動作序列。