使用模型上下文協定與 Quarkus 和 Langchain4j
1.概述
隨著大型語言模型 (LLM) 的出現,越來越多的團隊將 AI 整合到他們的應用程式中。 AI 整合不再侷限於簡單的問答。模型上下文協定 (MCP) 是一個有助於此類整合的概念。
在本教程中,我們將學習如何使用 Quarkus 和 LangChain4J 建立模型上下文協定 (LLM) 伺服器和用戶端。我們將創建一個簡單的聊天機器人,並透過 MCP 伺服器擴展 LLM 的功能,以執行一些簡單的自訂任務,例如根據時區和伺服器 JVM 的資訊取得當前日期。
2. 模型上下文協定 101
Anthropic 於 2024 年 11 月開源了其模型上下文協定(MCP) 。 MCP 提供了一種標準化的方式,將 AI 驅動的應用程式與外部資料來源連接起來。在深入程式碼之前,我們先來了解為什麼需要 MCP,以及它是什麼。
2.1. 為什麼我們需要模型上下文協定?
隨著人工智慧應用的普及,人們越來越期望透過法學碩士 (LLM) 來揭示特定組織和應用的「上下文」。因此,將此類上下文整合到人工智慧工作流程中也需要一些考慮。
上下文資訊可以從多種不同的資料來源整合到 AI 工作流程中,例如資料庫、檔案系統、搜尋引擎和其他工具。由於資料來源種類繁多,且底層連接方式各異,將這些情境資訊整合到 AI 整合中面臨巨大的挑戰。
這種整合可以嵌入到AI應用程式本身,但這可能會增加此類應用程式的複雜性。此外,許多組織已經擁有大量現有的內部應用服務,提供各種各樣的資訊。我們理想情況下希望能夠將現有服務與AI集成,而無需緊密耦合。
2.2. 引入模型上下文協議
我們現在明白,需要某種協議來幫助整合來自各種來源的上下文。模型上下文協定 (MCP) 滿足了這項需求。透過 MCP,我們可以在原生 LLM 之上建立複雜的代理和工作流程,而無需將它們緊密耦合。
這類似於使用者介面透過使用 REST API 與後端通訊的方式。一旦建立了 API 契約,只要 API 契約不變,UI 和後端都可以獨立修改。
我們注意到,即使 LLM 經過大量資料訓練,並且其記憶中儲存了大量訊息,它們仍然無法感知當前資訊或專有資訊。探索 MCP 的一個簡單方法是嘗試一些允許 LLM 查詢此類資訊的簡單服務。
在深入研究程式碼之前,讓我們快速了解一下 MCP 架構及其元件:
MCP 採用客戶端-伺服器架構,包含以下幾個關鍵元件:
- MCP Host是我們的主要應用程序,它與 LLM 集成,需要與外部資料來源連接
- MCP 用戶端是與 MCP 伺服器建立並維持 1:1 連線的元件
- MCP 伺服器是與外部資料來源整合並公開與其互動功能的元件
為了處理客戶端和伺服器之間的通信,MCP 還提供了兩個傳輸通道:
- **標準輸入/輸出 (stdio)**傳輸類型用於透過標準輸入和輸出流與本地進程和命令列工具進行通訊。
- 用於用戶端和伺服器之間基於 HTTP 的通訊的**伺服器發送事件 (SSE)**傳輸類型。
MCP 是一個複雜而龐大的主題;我們可以參考官方文件來了解更多資訊。
3. 建立自訂 MCP 伺服器
市面上有不少預置的MCP 伺服器,只需少量設定和配置即可使用。本文將介紹如何使用 Quarkus 建立自己的MCP 伺服器。
為此,我們將使用 Quarkus 建立兩個簡單的功能工具。一個工具根據時區取得當前日期,另一個工具提供有關伺服器 JVM 的資訊。
3.1. 建立 Quarkus MCP 伺服器項目
我們需要先安裝 JDK 和 Quarkus CLI。滿足這些先決條件後,我們就可以建立一個新專案:
quarkus create app --no-code -x qute,quarkus-mcp-server-sse quarkus-mcp-server
這應該會在名為quarkus-mcp-server
資料夾下建立一個新項目,其中包含 Quarkus 項目的基本鷹架。
3.2. 依賴項
使用quarkus
CLI 建立的專案會根據我們在命令中指定的擴充功能自動新增核心相依性( quarkus-arc
和quarkus-junit5
)以及相關的附加相依性。在我們的例子中,這些擴充功能是qute
和quarkus-mcp-server-sse
:
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkiverse.mcp</groupId>
<artifactId>quarkus-mcp-server-sse</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-qute</artifactId>
</dependency>
</dependencies>
我們現在可以繼續創建 LLM 稍後可以使用的工具。
3.3. 定義和公開自訂工具
我們將添加一些工具。大多數 LLM 在沒有工具的情況下無法取得當前資訊。因此,如果我們向 LLM 詢問當前日期和時間,它很可能會返回 LLM 訓練資料凍結的日期和時間。
因此,為了透過 MCP 伺服器測試 LLM 功能的增強,我們將建立一個簡單的工具,以使用者(或函數呼叫者)的時區提供當前日期和時間:
@Tool(description = "Get the current time in a specific timezone.")
public String getTimeInTimezone(
@ToolArg(description = "Timezone ID (eg, America/Los_Angeles)") String timezoneId) {
try {
ZoneId zoneId = ZoneId.of(timezoneId);
ZonedDateTime zonedDateTime = ZonedDateTime.now(zoneId);
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(java.time.format.FormatStyle.FULL)
.withLocale(Locale.getDefault());
return zonedDateTime.format(formatter);
} catch (Exception e) {
return "Invalid timezone ID: " + timezoneId + ". Please provide a valid IANA timezone ID.";
}
}
我們將這個功能工具封裝在一個名為ToolBox
的普通 Java 類別中。 Quarkus的@Tool (io.quarkiverse.mcp.server.Tool)
註解告訴 Quarkus 框架,被註解的方法將作為 MCP 伺服器中的一個工具公開。
此外, @ToolArg (io.quarkiverse.mcp.server.ToolArg)
註釋告訴 Quarkus 框架,註釋的函數參數應該與提供的描述一起公開。
這裡要注意的是, description
欄位應該清楚地指定函數呼叫者所需的訊息,以便正確使用該函數。**描述有助於法學碩士 (LLM) 識別工具的用途,從而在回答最終用戶的問題時選擇合適的工具。**
我們將添加另一個函數來獲取有關 MCP 伺服器正在運行的 JVM 的資訊:
@Tool(description = "Provides JVM system information such as available processors, free memory, total memory, and max memory.")
public String getJVMInfo() {
StringBuilder systemInfo = new StringBuilder();
// Get available processors
int availableProcessors = Runtime.getRuntime().availableProcessors();
systemInfo.append("Available processors (cores): ").append(availableProcessors).append("\n");
// Get free memory
long freeMemory = Runtime.getRuntime().freeMemory();
systemInfo.append("Free memory (bytes): ").append(freeMemory).append("\n");
// Get total memory
long totalMemory = Runtime.getRuntime().totalMemory();
systemInfo.append("Total memory (bytes): ").append(totalMemory).append("\n");
// Get max memory
long maxMemory = Runtime.getRuntime().maxMemory();
systemInfo.append("Max memory (bytes): ").append(maxMemory).append("\n");
return systemInfo.toString();
}
3.4. 運行 MCP 伺服器
現在我們有幾個可用的工具,我們可以在開發模式下運行 Quarkus 伺服器來快速測試它們。
為了簡化部署和開發,我們將伺服器打包為uber-jar
。這樣就可以使用mvn install
並將其作為 JAR 發佈到 Maven 儲存庫,從而更輕鬆地與我們和其他人共用和運行。
Quarkus 預設使用 HTTP 連接埠 8080。因此,我們將連接埠號碼改為9000
,因為稍後 MCP 用戶端套件需要用到 8080 連接埠。我們在application.properties
檔案中進行了以下兩項變更:
quarkus.package.jar.type=uber-jar
quarkus.http.port=9000
現在,我們可以使用quarkus
CLI 來運行 Quarkus 開發模式。一旦開發模式伺服器啟動並運行,我們預計它將在http://localhost:9000/q/dev-ui/
上運行:
quarkus dev
由於我們使用@Tool
註解將函數揭露為 MCP 伺服器的一部分,因此開發模式會自動新增一個 MCP 伺服器擴充功能供我們使用。這使我們能夠使用 Quarkus 開發模式提供的 Open API / Postman 風格的工具呼叫介面快速測試這些工具。
3.5. 測試工具
我們現在將使用 Quarkus Dev UI 中的 MCP 伺服器擴充功能來執行一些快速測試。
首先,我們導航到 Dev UI 下的「工具」標籤。我們可以點擊 MCP 伺服器擴充功能下的Tools
連結:
它也可以透過以下 URL 存取http://localhost:9000/q/dev-ui/io.quarkiverse.mcp.quarkus-mcp-server-sse/tools.
我們注意到,我們定義的兩個工具都可以使用Call
操作按鈕來執行快速測試:
讓我們透過傳入一個特定的時區來呼叫getTimeInTimezone
工具。工具呼叫者已經根據我們的描述提供了一個預先填入的 JSON 資料。我們對其進行修改,使其能夠提供一些實際的值:
{
"timezoneId": "Asia/Kolkata"
}
填寫完輸入後,我們點擊Call
按鈕,回應部分立即以 JSON 格式提供適當的輸出:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"isError": false,
"content": [
{
"text": "Sunday, May 18, 2025, 7:33:26 PM India Standard Time",
"type": "text"
}
]
}
}
類似地,我們也可以測試另一個功能。現在我們已經準備好伺服器端了,讓我們轉到客戶端。
4. 建立自訂 MCP 用戶端
我們已經準備好一個 MCP 伺服器和一些自訂工具。現在是時候使用這些工具與 LLM 進行聊天了。為此,我們將使用 Quarkus 和 LangChain4j。對於 LLM API,我們將使用在本機上執行的 Ollama 並以 Mistral 作為模型。當然,我們也可以使用 LangChain4j 庫支援的任何其他 LLM。
4.1. 建立 Quarkus MCP 用戶端項目
我們可以使用quarkus cli
來建立一個新專案:
quarkus create app --no-code -x langchain4j-mcp,langchain4j-ollama,vertx-http quarkus-mcp-client
這應該在名為quarkus-mcp-client
資料夾下建立一個名為 Quarkus 專案的基本鷹架的新專案。
4.2. 依賴項
使用quarkus
CLI 建立的專案會根據我們在命令中指定的擴充功能自動新增核心相依性( quarkus-arc
和quarkus-junit5
)以及相關的附加相依性。在我們的例子中,這些擴展是langchain4j-mcp
、 langchain4j-ollama
和vertx-http
:
<dependency>
<groupId>io.quarkiverse.langchain4j</groupId>
<artifactId>quarkus-langchain4j-mcp</artifactId>
<version>1.0.0.CR2</version>
</dependency>
<dependency>
<groupId>io.quarkiverse.langchain4j</groupId>
<artifactId>quarkus-langchain4j-ollama</artifactId>
<version>1.0.0.CR2</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http</artifactId>
</dependency>
4.3. 定義聊天機器人
我們將利用 Quarkus 和 LangChain4j 定義具有自訂系統提示的聊天服務。
我們建立一個名為McpClientAI
的接口,並使用@RegisterAiService
註解。 Quarkus 使用此註解,透過 LangChain4j 和已設定的 LLM 自動產生 LLM 用戶端服務:
@RegisterAiService
@SessionScoped
public interface McpClientAI {
@SystemMessage("""
You are a knowledgeable and helpful assistant powered by Mistral.
You can answer user questions and provide clear, concise, and accurate information.
You also have access to a set of tools via an MCP server.
When using a tool, always convert the tool's response into a natural, human-readable answer.
If the user's question is unclear, politely ask for clarification.
If the question does not require tool usage, answer it directly using your own knowledge.
Always communicate in a friendly and professional manner, and ensure your responses are easy to understand.
"""
)
@McpToolBox("default")
String chat(@UserMessage String question);
}
此外,我們使用@SystemMessage
註解將自訂系統提示附加到chat()
方法。此系統提示會提示 LLM 可以透過 MCP 伺服器存取工具,並應使用這些工具來回應使用者查詢。
我們使用@McpToolBox
註解告訴 Quarkus,MCP 伺服器已配置為「 default”
。 Quarkus應該將其與 McpClientAI 服務一起使用。
現在,我們需要告訴 Quarkus 在哪裡可以找到使用 AI 服務的 LLM 和工具。
4.4. 連接 LLM 和工具
Quarkus 提供了許多設定選項,可自動設定 LLM 用戶端和 MCP 用戶端。我們需要在 Quarkus 的application.properties
檔案中設定一些屬性,以便連接 LLM 和 MCP 伺服器。
首先,我們為所有 LLM 呼叫設定一個逾時時間。這是一個可選設置,但如果在速度較慢的機器(例如開發人員桌面)上運行 LLM,則可能需要將其配置為更大的值:
quarkus.langchain4j.timeout=10s
為了進行測試,我們將在本機電腦的11434
連接埠上執行Ollama伺服器,並在 Ollama 伺服器上載入Mistral模型。我們透過相應的屬性將這些資訊告知 Quarkus:
quarkus.langchain4j.chat-model.provider=ollama
quarkus.langchain4j.ollama.chat-model.model-id=mistral
quarkus.langchain4j.ollama.base-url=http://localhost:11434
接下來,我們將 Quarkus 指向先前建立的 MCP 伺服器。根據我們的配置,該伺服器運行在9000
連接埠上。此外,我們注意到 Quarkus MCP 伺服器已自動配置為 URI /mcp/sse
:
quarkus.langchain4j.mcp.default.transport-type=http
quarkus.langchain4j.mcp.default.url=http://localhost:9000/mcp/sse
透過此配置,Quarkus 會自動建立與 MCP 伺服器的連接,並使這些工具可供我們的聊天服務使用。屬性名稱包含單字“default”
,該名稱應與我們在@McpToolBox
註解參數中配置的名稱相符。現在,我們可以使用 Quarkus CLI 來運行 Quarkus 開發模式。開發模式伺服器啟動並運行後,我們預計它會在http://localhost:8080/q/dev-ui/
上運行:
quarkus dev
由於我們使用@RegisterAiService
註解建立了聊天服務,因此開發模式會自動新增一個名為 LangChain4j core 的擴充功能供我們使用。這使我們能夠快速測試聊天用戶端,而無需編寫其他服務。 LangChain4j 程式碼擴充功能提供了一個可立即使用的聊天 UI,可連接到我們的聊天服務。
4.5. 測試聊天服務
現在,我們將使用 Quarkus Dev UI 中的聊天 UI 擴充功能來執行一些快速測試。首先,我們導航到 Dev UI 下的「工具」標籤。我們可以點擊 LangChain4j Core 擴充功能下的Chat
連結。此連結也可透過以下 URL 存取http://localhost:8080/q/dev-ui/io.quarkiverse.langchain4j.quarkus-langchain4j-core/chat.
我們可以使用此工具與 LLM 聊天。我們用簡單的英語提問來詢問時間和系統信息,LLM 現在使用我們 MCP 伺服器提供的工具來提供最新資訊。
範例聊天如下圖所示:
我
你好人工智慧
您好!今天我能幫您什麼忙?如果您需要特定任務的協助,請隨時提出,我會盡力幫助您。以下是我可以做的事情:– 使用 `getSystemInfo()` 獲取系統信息,例如可用處理器、可用內存、總內存和最大內存。 – 使用 `getTimeInTimezone(timezoneId)` 取得特定時區的目前時間,其中 `timezoneId` 是一個字串,代表所需的時區(例如「America/Los_Angeles」)。如果您有任何問題或需要進一步的協助,請告訴我!我
給我係統訊息
工具
請求執行以下工具:請求ID = undefined,工具名稱 = getSystemInfo,參數 = { }
工具
請求 ID = 未定義、工具名稱 = getSystemInfo、狀態 = 的工具執行結果可用處理器(核心):8 可用記憶體(位元組):94046840 總記憶體(位元組):182452224 最大記憶體(位元組):4171235328
人工智慧
以下是您裝置的系統資訊: – 可用處理器(核心)數量:8 – 可用記憶體:94,046,840 位元組 – 總記憶體:182,452,224 位元組 – 最大記憶體:4,171,235,328 位元組您可以根據需要在程式中使用此資訊。
我
印度現在幾點
工具
請求執行以下工具:請求 ID = 未定義,工具名稱 = getTimeInTimezone,參數 = {“timezoneId”:“Asia/Kolkata”}
工具
請求 ID = 未定義、工具名稱 = getTimeInTimezone、狀態 = 2025 年 5 月 10 日星期六下午 4:26:37 印度標準時間的工具執行結果
人工智慧
印度目前時間(印度標準時間)是:2025年5月10日星期六下午4:26:37。
就這樣,我們現在已經建立了一個包含自訂工具的聊天服務。現在我們可以以此服務為基礎,開發其他服務,例如用於與 LLM 聊天的 HTTP API。
然而,需要注意的是,即使這些工具可用,是否使用也取決於法學碩士(LLM)的能力。有時,即使工具可以輔助解答,LLM 也可能直接忽略。**因此,可能需要調整系統提示和工具描述,以協助 LLM 識別何時使用工具**。
5. 結論
在本文中,我們使用 Quarkus 配置了一個 MCP 伺服器,並配置了一個連接到該 MCP 伺服器的獨立 MCP 用戶端應用程式。我們使用 Quarkus 和 LangChain4j 建立了客戶端應用程式。在後台,我們將其連接到 Ollama API,以便連接到 Mistral LLM。
我們透過提供獲取 JVM 系統資訊和當前時間的工具來增強 Mistral LLM 的功能。如果沒有這些工具,LLM 就無法取得這些動態資訊。我們在本文中使用的這些工具不太可能在實際專案中使用。即便如此,它們確實展示了透過提供我們自己的自訂工具來增強 LLM 功能的可能性。
最後,我們注意到 MCP 伺服器允許我們使用自己的程式碼提供各種功能,而 MCP 用戶端允許我們輕鬆地將這些工具整合到 LLM 連接的應用程式中。
與往常一樣,程式碼可在 GitHub 上取得。