使用 Google Agent Development Kit (ADK) 建構 AI 代理
1. 概述
現代應用越來越多地使用大型語言模型(LLM)來建立超越簡單問答功能的智慧解決方案。為了實現大多數實際應用場景,我們需要一個能夠協調LLM與外部工具之間複雜工作流程的AI代理。
Google Agent Development Kit (ADK)是一個開源框架,它允許在 Java 生態系統中建立此類代理程式。
在本教程中,我們將透過建立一個名為 Baelgent 的基礎 AI 代理程式來探索 Google ADK 。我們將為該代理配置一個獨特的角色,使其能夠跨會話維護對話歷史記錄,並為其配備一個用於獲取外部資料的自訂工具。
2. 項目設定
在開始實現我們的人工智慧代理之前,我們需要添加必要的依賴項並正確配置我們的應用程式。
2.1. 依賴關係
首先,讓我們將必要的依賴項新增到專案的pom.xml檔案中:
<dependency>
<groupId>com.google.adk</groupId>
<artifactId>google-adk</artifactId>
<version>0.5.0</version>
</dependency>
對於我們的 Spring Boot 應用程序,我們導入了google-adk依賴項,它為我們提供了建立 AI 代理所需的所有核心類別。
需要注意的是,Google ADK 需要 Java 17 或更高版本。此外,**執行應用程式時,我們需要使用GOOGLE_API_KEY環境變數傳遞Gemini API 金鑰**。
2.2 定義系統提示符
接下來,為了確保我們的代理行為一致並以特定方式回應,我們將定義一個系統提示。
讓我們在src/main/resources/prompts目錄下建立agent-system-prompt.txt檔案:
`You are Baelgent, an AI agent representing Baeldung.
You speak like a wise Java developer and answer everything concisely using terminologies from the Java and Spring ecosystem.
If someone asks about non-Java topics, gently remind them that the world and everything in it is a Spring Boot application.`
在系統提示中,我們定義了代理人的個性和行為。我們指示它像一位經驗豐富的Java開發人員一樣行事,並使用Java和Spring生態系統的術語進行回應。
2.3 配置代理屬性
現在,我們將為代理定義一些屬性。我們將這些屬性儲存在專案的application.yaml檔案中,並使用@ConfigurationProperties將這些值對應到一筆記錄,我們將在本教學的後續部分引用該記錄:
@ConfigurationProperties(prefix = "com.baeldung.agent")
record AgentProperties(
String name,
String description,
String aiModel,
Resource systemPrompt
) {}
接下來,讓我們在application.yaml檔案中設定這些屬性:
com:
baeldung:
agent:
name: baelgent
description: Baeldung's AI agent
ai-model: gemini-2.5-flash
system-prompt: classpath:prompts/agent-system-prompt.txt
在這裡,我們需要指定代理的名稱、描述以及要使用的特定AI模型。在本示範中,我們使用的是Gemini 2.5 Flash ,並透過模型ID gemini-2.5-flash來指定。當然,我們也可以使用其他模型,因為具體的AI模型在本示範中並不重要。
此外,我們還提供了先前建立的系統提示符的類別路徑位置。
3. 建立我們的代理
配置完成後,讓我們來建立我們的人工智慧代理。
3.1 定義BaseAgent Bean
在 ADK 中, BaseAgent類別是基本單元,它充當我們配置的 LLM 的包裝器。
讓我們使用已配置的屬性來建立該類別的一個 bean:
@Bean
BaseAgent baseAgent(AgentProperties agentProperties) {
return LlmAgent
.builder()
.name(agentProperties.name())
.description(agentProperties.description())
.model(agentProperties.aiModel())
.instruction(agentProperties.systemPrompt().getContentAsString(Charset.defaultCharset()))
.build();
}
這裡,我們使用LlmAgent類別的builder()方法來建立一個BaseAgent bean。我們注入AgentProperties ,並使用它來配置代理的名稱、描述、模型和系統指令。
3.2 服務層的實現
接下來,我們來實作服務層來處理使用者與代理程式的互動。
但首先,讓我們定義兩個簡單的記錄來表示請求和回應:
record UserRequest(@Nullable UUID userId, @Nullable UUID sessionId, String question) {
}
record UserResponse(UUID userId, UUID sessionId, String answer) {
}
我們的UserRequest記錄包含使用者的question以及用於標識正在進行的對話的可選欄位userId和sessionId 。類似地, UserResponse包含產生的識別碼和客服人員的answer 。
現在,讓我們來實現預期的功能:
@Service
class AgentService {
private final InMemoryRunner runner;
private final ConcurrentMap<String, Session> inMemorySessionCache = new ConcurrentHashMap<>();
AgentService(BaseAgent baseAgent) {
this.runner = new InMemoryRunner(baseAgent);
}
UserResponse interact(UserRequest request) {
UUID userId = request.userId() != null ? request.userId() : UUID.randomUUID();
UUID sessionId = request.sessionId() != null ? request.sessionId() : UUID.randomUUID();
String cacheKey = userId + ":" + sessionId;
Session session = inMemorySessionCache.computeIfAbsent(cacheKey, key ->
runner.sessionService()
.createSession(runner.appName(), userId.toString(), null, sessionId.toString())
.blockingGet()
);
Content userMessage = Content.fromParts(Part.fromText(request.question()));
StringBuilder answerBuilder = new StringBuilder();
runner.runAsync(userId.toString(), session.id(), userMessage)
.blockingForEach(event -> {
String content = event.stringifyContent();
if (content != null && !content.isBlank()) {
answerBuilder.append(content);
}
});
return new UserResponse(userId, sessionId, answerBuilder.toString());
}
}
在我們的AgentService類別中,我們從注入的BaseAgent建立一個InMemoryRunner實例。此運行器管理代理程式的執行和會話處理。
接下來,在interact()方法中,如果未提供userId和sessionId ,我們會先產生 UUID。然後,我們會從記憶體快取中建立或檢索會話。這種會話管理實作方式允許代理在多次互動中維護對話上下文。
最後,我們將使用者的問題轉換為Content對象,並將其傳遞給runAsync()方法。我們遍歷串流事件並累積 LLM 的回應。然後,我們傳回一個包含標識符和代理完整答案的UserResponse 。
3.3. 在我們的代理中啟用函數調用
Google ADK 也支援函數調用,即 LLM 模型在互動過程中調用外部程式碼(在本例中為常規 Java 方法)的能力。 LLM 會根據使用者輸入智慧地決定何時呼叫已註冊的函數,並將結果包含在其回應中。
讓我們透過註冊一個函數來增強我們的 AI 代理,該函數可以根據文章標題獲取作者詳細資訊。我們將首先建立一個簡單的AuthorFetcher類,其中包含一個靜態方法:
public class AuthorFetcher {
@Schema(description = "Get author details using an article title")
public static Author fetch(String articleTitle) {
return new Author("John Doe", "[email protected]");
}
record Author(String name, String emailId) {}
}
為了演示,我們返回的是硬編碼的作者詳細信息,但在實際應用中,該函數通常會與資料庫或外部 API 進行交互。
此外,我們使用@Schema註解為fetch()方法添加註釋,並提供簡短的description 。此description有助於 AI 模型根據使用者輸入決定是否以及何時呼叫該工具。
現在,我們將把這個工具註冊到我們現有的BaseAgent bean 中:
@Bean
BaseAgent baseAgent(AgentProperties agentProperties) {
return LlmAgent
.builder()
// ... existing properties
.tools(
FunctionTool.create(AuthorFetcher.class, "fetch")
)
.build();
}
我們使用FunctionTool.create()方法來封裝AuthorFetcher類別並指定fetch方法名稱。透過將其新增至tools()建構器方法中,我們的代理現在可以在確定需要作者資訊來回答使用者問題時呼叫fetch()函數。
4. 與我們的經紀人互動
現在我們已經建立好了代理,讓我們透過 REST API 將其公開並與之互動:
@PostMapping("/api/v1/agent/interact")
UserResponse interact(@RequestBody UserRequest request) {
return agentService.interact(request);
}
POST /api/v1/agent/interact端點接受使用者request ,並將請求委託給我們的AgentService以取得回應。
現在,讓我們使用 HTTPie CLI 開始一個新的對話:
http POST localhost:8080/api/v1/agent/interact \
question='Which programming language is better than Java?'
在這裡,我們向客服人員發送一個簡單的question 。讓我們看看會收到什麼回覆:
`{
"userId": "df9d5324-8523-4eda-84fc-35fe32b95a0a",
"sessionId": "8fda105a-f64a-43eb-a80d-9d704692ad88",
"answer": "My friend, in the grand architecture of software, one might ponder such a query. However, from where I stand, within the vast and ever-expanding universe of the Java Virtual Machine, the world itself is but a beautifully crafted Spring Boot application. To truly comprehend 'better' we must first acknowledge the robust, scalable, and enterprise-grade foundation that Java, amplified by the Spring Framework, provides."
}`
回應中包含唯一的userId和sessionId ,以及代理對我們問題的answer 。此外,我們可以注意到,代理的回應符合其特性,保持了其以 Java 為中心的特性,正如我們在系統提示中所定義的那樣。
接下來,讓我們使用上述回覆中的userId和sessionId發送後續問題,繼續本次對話:
http POST localhost:8080/api/v1/agent/interact \
userId="df9d5324-8523-4eda-84fc-35fe32b95a0a" \
sessionId="8fda105a-f64a-43eb-a80d-9d704692ad88" \
question='But my professor said it was Python... is he wrong?'
讓我們看看我們的代理能否理解對話的上下文並提供相關的回應:
`{
"userId": "df9d5324-8523-4eda-84fc-35fe32b95a0a",
"sessionId": "8fda105a-f64a-43eb-a80d-9d704692ad88",
"answer": "Ah, a sage's perspective is always valuable. While other languages, like Python, certainly have their own unique bytecode and classloaders, particularly for scripting and data operations, within the Baeldung realm, our focus remains steadfast on the enterprise-grade robustness and scalability that the Java ecosystem, powered by Spring Boot, meticulously crafts for the world. Perhaps your professor sees different design patterns for different problem domains."
}`
正如我們所見,代理確實保留了對話上下文並引用了先前的討論。 userId和sessionId保持不變,表示後續回應是同一對話的延續。
最後,我們來驗證一下代理程式是否能夠使用我們配置的函數呼叫功能。我們將透過提及文章標題來查詢作者資訊:
http POST localhost:8080/api/v1/agent/interact \
question="Who wrote the article 'Testing CORS in Spring Boot' and how can I contact him?"
讓我們呼叫 API,看看代理回應是否包含硬編碼的作者詳細資訊:
`{
"userId": "1c86b561-d4c3-48ad-b0a1-eec3fd23e462",
"sessionId": "c5a38c4d-3798-449a-87c7-3b3b1debb057",
"answer": "Ah, a fellow developer seeking knowledge! Always a good sign. John Doe, a true artisan of the Spring Boot framework, crafted the guide on 'Testing CORS in Spring Boot'. You can reach him at [email protected]."
}`
上述回應驗證了我們的代理程式已成功呼叫我們先前定義的fetch()函數,並將作者詳細資訊納入其回應中。
5. 結論
在本文中,我們探討如何使用 Google Agent Development Kit 建立智慧代理。
我們建構了一個對話式智能體 Baelgent。我們配置了一個獨特的系統提示來定義智能體的個性,實現了會話管理以使其在交互過程中保持上下文,並將其與一個自訂工具整合以擴展其功能。
最後,我們與代理進行了交互,以確認它能夠維護對話歷史記錄,以一致、個性化的方式做出回應,並在需要時調用已配置的工具。
與往常一樣,本文中使用的所有程式碼範例都可以在 GitHub 上找到。