Cymbal Transit:使用 LangChain4J 和 MCP Toolbox Java SDK 的多智能体系统

1. 概览

现代旅客期望获得对话式体验。他们不想使用复杂的界面过滤条件,而是想直接提问:“我可以带狗乘坐上午 9 点前往波士顿的巴士吗?”这需要一个能够对非结构化数据(PDF 政策)和结构化数据(SQL 调度)进行推理的代理。

在本实验中,我们将使用以下工具构建 Cymbal Transit Agent

  • LangChain4j:用于 AI 编排的首要 Java 框架。
  • AlloyDB:一款与 PostgreSQL 兼容的高性能数据库。
  • MCP Toolbox Java SDK:一种标准化方式,可将 Java 智能体连接到外部工具和数据源。

构建内容

e68388d533c9997e.png

Cymbal Bus Agent,一个 Java Spring Boot 应用,包含:

  1. AlloyDB 数据库和 MCP Toolbox Java SDK,用于与代理进行工具编排。
  2. Cloud Run,用于部署工具箱和应用(代理部署)。
  3. 适用于 Spring Boot 应用(使用 Java 17)中代理和 LLM 框架的 LangChain4J 库。

学习内容

  • 如何使用 LangChain4J 创建专门的代理和子代理,这些代理和子代理使用 MCP Toolbox for Databases Java SDK 进行编排
  • 如何设置和使用 AlloyDB for Data and AI。
  • 如何使用 MCP Toolbox 将代理连接到 AlloyDB 数据工具。
  • 如何使用 Cloud Run 部署解决方案或在本地运行解决方案。

架构

  1. AlloyDB for PostgreSQL:作为高性能的运营数据库,用于存储我们的路线、政策和预订记录。它支持向量搜索和检索。
  2. MCP Toolbox for Databases Java SDK:充当“编排指挥家”,将 AlloyDB 数据公开为智能体可调用的可执行工具。

借助 MCP Toolbox Java SDK,您可以轻松地将智能体与数据库工具编排在一起,以构建企业级应用。

  1. LangChain4J:一个开源 Java 库,可简化将大语言模型 (LLM) 集成到 Java 应用中的过程。它提供用于构建 AI 赋能的应用(包括聊天机器人、智能体和检索增强生成 (RAG) 系统)的工具和抽象。
  2. Cloud Run:这是一个全托管式无服务器平台,可让您轻松使用任何语言、任何库、任何二进制文件快速构建和部署应用或网站。您可以使用自己喜欢的语言、框架和库编写代码,将其打包为容器,然后运行“gcloud run deploy”命令,您的应用立刻就可以上线,并具有在生产环境中运行所需的一切。构建容器完全是可选操作。如果您使用的是 Go、Node.js、Python、Java、.NET Core 或 Ruby,则可以使用基于来源的部署选项,它会按照您所用语言的最佳实践构建容器。

要求

  • 一个浏览器,例如 ChromeFirefox
  • 启用了结算功能的 Google Cloud 项目。
  • 基本熟悉 SQL 和 Java。

2. 准备工作

创建项目

  1. Google Cloud Console 的项目选择器页面上,选择或创建一个 Google Cloud 项目
  2. 确保您的 Cloud 项目已启用结算功能。了解如何检查项目是否已启用结算功能
  1. 您将使用 Cloud Shell,它是在 Google Cloud 中运行的命令行环境。点击 Google Cloud 控制台顶部的“激活 Cloud Shell”。

“激活 Cloud Shell”按钮图片

  1. 连接到 Cloud Shell 后,您可以使用以下命令检查自己是否已通过身份验证,以及项目是否已设置为您的项目 ID:
gcloud auth list
  1. 在 Cloud Shell 中运行以下命令,以确认 gcloud 命令了解您的项目。
gcloud config list project
  1. 如果项目未设置,请使用以下命令进行设置:
gcloud config set project <YOUR_PROJECT_ID>
  1. 启用必需的 API:点击链接并启用 API。

或者,您也可以使用 gcloud 命令来完成此操作。如需了解 gcloud 命令和用法,请参阅文档

注意事项和问题排查

“幽灵项目” 综合征

您运行了 gcloud config set project,但实际上在控制台界面中查看的是另一个项目。检查左上角下拉菜单中的项目 ID!

结算 路障

您已启用项目,但忘记了结算账号。AlloyDB 是一款高性能引擎;如果“油箱”(结算)为空,它将无法启动。

API 传播 延迟

您点击了“启用 API”,但命令行仍显示 Service Not Enabled。等待 60 秒。云端需要一些时间来唤醒其神经元。

配额 Quags

如果您使用的是全新试用账号,则可能会达到 AlloyDB 实例的区域配额。如果 us-central1 失败,则尝试 us-east1

“隐藏”服务代理

有时,AlloyDB 服务代理不会自动获得 aiplatform.user 角色。如果您的 SQL 查询之后无法与 Gemini 对话,通常是此问题所致。

3. 数据库设置

我们应用的核心是 AlloyDB for PostgreSQL。我们利用了其强大的向量功能和集成的列式引擎,为 5 万多条 SCM 记录生成了嵌入。这实现了近乎实时的向量分析,使我们的代理能够在数毫秒内识别海量数据集中的库存异常或物流风险。

在本实验中,我们将使用 AlloyDB 作为测试数据的数据库。它使用集群来保存所有资源,例如数据库和日志。每个集群都有一个主实例,可提供对数据的访问点。表将包含实际数据。

我们来创建 AlloyDB 集群、实例和表,以便加载测试数据集。

  1. 点击相应按钮,或将以下链接复制到已登录 Google Cloud 控制台用户的浏览器中。

或者,您也可以从已兑换结算账号的项目中前往 Cloud Shell 终端,然后使用以下命令克隆 GitHub 代码库 并前往相应项目:

git clone https://github.com/AbiramiSukumaran/easy-alloydb-setup

cd easy-alloydb-setup
  1. 完成此步骤后,代码库将克隆到本地 Cloud Shell 编辑器,您将能够从项目文件夹中运行以下命令(请务必确保您位于项目目录中):
sh run.sh
  1. 现在,使用界面(点击终端中的链接或点击终端中的“在网页上预览”链接)。
  2. 输入项目 ID、集群名称和实例名称等详细信息,即可开始使用。
  3. 在日志滚动时,您可以去喝杯咖啡,然后点击此处了解其幕后运作方式。

注意事项和问题排查

“耐心”问题

数据库集群是重型基础架构。如果您因 Cloud Shell 会话“看起来卡住了”而刷新页面或终止会话,最终可能会得到一个“幽灵”实例,该实例已部分完成预配,但无法在不进行人工干预的情况下删除。

地区不匹配

如果您在 us-central1 中启用了 API,但尝试在 asia-south1 中预配集群,则可能会遇到配额问题或服务账号权限延迟。在整个实验过程中,请坚持使用一个区域!

僵尸集群

如果您之前曾使用过某个集群名称,但未删除该集群,脚本可能会显示该集群名称已存在。集群名称在项目中必须是唯一的。

Cloud Shell 超时

如果您的咖啡休息时间为 30 分钟,Cloud Shell 可能会进入休眠状态并断开 sh run.sh 进程。让标签页保持活跃状态!

4. 架构配置

在 AlloyDB 集群和实例运行后,前往 AlloyDB Studio SQL 编辑器,启用 AI 扩展程序并预配架构。

1e3ac974b18a8113.png

您可能需要等待实例完成创建。完成后,使用您在创建集群时创建的凭据登录 AlloyDB。使用以下数据向 PostgreSQL 进行身份验证:

  • 用户名:“postgres
  • 数据库:“postgres
  • 密码:“alloydb”(或您在创建时设置的任何密码)

成功通过身份验证进入 AlloyDB Studio 后,您可以在编辑器中输入 SQL 命令。您可以使用最后一个窗口右侧的加号添加多个编辑器窗口。

28cb9a8b6aa0789f.png

您将在编辑器窗口中输入 AlloyDB 命令,并根据需要使用“运行”“格式”和“清除”选项。

启用扩展程序

我们将使用扩展程序 pgvectorgoogle_ml_integration 来构建此应用。借助 pgvector 扩展程序,您可以存储和搜索向量嵌入。google_ml_integration 扩展程序提供用于访问 Vertex AI 预测端点以在 SQL 中获取预测结果的函数。运行以下 DDL 以启用这些扩展程序:

CREATE EXTENSION IF NOT EXISTS google_ml_integration CASCADE;
CREATE EXTENSION IF NOT EXISTS vector;

授予权限

运行以下语句,以授予对“embedding”函数的执行权限:

GRANT EXECUTE ON FUNCTION embedding TO postgres;

为 AlloyDB 服务账号授予 Vertex AI User 角色

Google Cloud IAM 控制台中,向 AlloyDB 服务账号(格式如下:service-<<PROJECT_NUMBER>>@gcp-sa-alloydb.iam.gserviceaccount.com)授予“Vertex AI 用户”角色。PROJECT_NUMBER 将包含您的项目编号。

或者,您也可以从 Cloud Shell 终端运行以下命令:

PROJECT_ID=$(gcloud config get-value project)


gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:service-$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")@gcp-sa-alloydb.iam.gserviceaccount.com" \
--role="roles/aiplatform.user"

创建表

您可以在 AlloyDB Studio 中使用以下 DDL 语句创建表:

DROP TABLE IF EXISTS transit_policies;
DROP TABLE IF EXISTS bus_schedules;
DROP TABLE IF EXISTS bookings;

-- Table 1: Transit Policies (Unstructured Data for RAG)
CREATE TABLE transit_policies (
    policy_id SERIAL PRIMARY KEY,
    category VARCHAR(50),
    policy_text TEXT,
    policy_embedding vector(768) 
);

-- Table 2: Intercity Bus Schedules (Structured Data)
CREATE TABLE bus_schedules (
    trip_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    origin_city VARCHAR(100),
    destination_city VARCHAR(100),
    departure_time TIMESTAMP,
    arrival_time TIMESTAMP,
    available_seats INT DEFAULT 50,
    ticket_price DECIMAL(6,2)
);

-- Table 3: Booking Ledger (Transactional Action Data)
CREATE TABLE bookings (
    booking_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    trip_id UUID REFERENCES bus_schedules(trip_id),
    passenger_id VARCHAR(100),
    status VARCHAR(20) DEFAULT 'CONFIRMED',
    booking_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

policy_embedding 列将允许存储某些文本字段的矢量值。

数据注入

运行以下 SQL 语句集,以批量将记录插入相应表中

  1. 在 AlloyDB 中原生插入非结构化政策并生成真实嵌入
-- 1. Insert Unstructured Policies and GENERATE REAL EMBEDDINGS natively in AlloyDB

INSERT INTO transit_policies (category, policy_text, policy_embedding) 
VALUES 
('Pets', 'Service animals are always welcome. Small pets (under 25 lbs) are allowed in secure carriers for a $25 fee. Large dogs are not permitted on standard coaches.', embedding('text-embedding-005', 'Service animals are always welcome. Small pets (under 25 lbs) are allowed in secure carriers for a $25 fee. Large dogs are not permitted on standard coaches.')),
('Luggage', 'Each passenger is allowed one carry-on (up to 15 lbs) and two stowed bags (up to 50 lbs each) free of charge. Additional bags cost $15 each.', embedding('text-embedding-005', 'Each passenger is allowed one carry-on (up to 15 lbs) and two stowed bags (up to 50 lbs each) free of charge. Additional bags cost $15 each.')),
('Refunds', 'Tickets are fully refundable up to 24 hours before departure. Within 24 hours, tickets can be exchanged for travel credit only.', embedding('text-embedding-005', 'Tickets are fully refundable up to 24 hours before departure. Within 24 hours, tickets can be exchanged for travel credit only.'));
  1. 使用 generate_series 为 7 天生成 200 多个实际安排
-- 2. Generate 200+ Realistic Schedules for the Next 7 Days using generate_series

INSERT INTO bus_schedules (origin_city, destination_city, departure_time, arrival_time, ticket_price, available_seats)
SELECT 
    origin,
    destination,
    -- Generate departures every 4 hours starting from tomorrow
    (CURRENT_DATE + 1) + (interval '4 hours' * seq) AS dep_time,
    (CURRENT_DATE + 1) + (interval '4 hours' * seq) + interval '4.5 hours' AS arr_time,
    ROUND((RANDOM() * 30 + 25)::numeric, 2) AS price, -- Random price between $25 and $55
    FLOOR(RANDOM() * 50 + 1) AS seats -- Random seats between 1 and 50
FROM 
    (VALUES 
        ('New York', 'Boston'), ('Boston', 'New York'),
        ('Philadelphia', 'Washington DC'), ('Washington DC', 'Philadelphia'),
        ('Seattle', 'Portland'), ('Portland', 'Seattle')
    ) AS routes(origin, destination)
CROSS JOIN generate_series(1, 40) AS seq; -- 6 routes * 40 time slots = 240 distinct trips ingested!

生成嵌入

嵌入会自动包含在插入语句中,并使用函数“embedding('text-embedding-005', '<<policytext>>')”插入到 transit_policies 表中。

注意事项和问题排查

“密码遗忘”循环

如果您使用了“一键”设置,但不记得密码,请前往控制台中的实例基本信息页面,然后点击“修改”以重置 postgres 密码。

“未找到扩展程序”错误

如果 CREATE EXTENSION 失败,通常是因为实例仍处于初始配置时的“维护”或“更新”状态。检查实例创建步骤是否完成,并在需要时等待几秒钟。

IAM 传播问题

您运行了 gcloud IAM 命令,但 SQL CALL 仍然因权限错误而失败。IAM 更改可能需要一段时间才能通过 Google 主干网传播。深吸一口气。***严重

  1. 有时,您的 AlloyDB 服务账号可能与我们在权限步骤中使用的现有格式不同。为 100% 确保 AlloyDB 服务账号具有 Vertex AI User 角色:前往 Google Cloud 控制台中的 AlloyDB 集群页面。点击您的集群,然后在概览标签页中,找到标记为服务账号的字段。
    复制该值,然后前往 IAM 并添加 Vertex AI User 角色。
  2. 此外,如果您跳过了“准备工作”部分中的“启用 API”步骤,则在从 AlloyDB 访问嵌入内容时会遇到问题。

向量维度不匹配

transit_policies 表的列 policy_embedding 设置为 VECTOR(768)。如果您稍后尝试使用其他模型(例如 1536 维模型),则插入操作会失败。坚持 text-embedding-005

项目 ID 拼写错误

create_model 调用中,如果您遗漏了方括号 « » 或错误地输入了项目 ID,模型注册看起来会成功,但在第一次实际查询期间会失败。请仔细检查您的字符串!

5. 工具和工具箱设置

MCP Toolbox for Databases 是一款适用于数据库的开源 MCP 服务器。它通过处理连接池、身份验证和更多复杂性问题,让您能够更轻松、更快速、更安全地开发工具。借助工具箱,您可以构建生成式 AI 工具,让代理访问数据库中的数据。

我们将 Model Context Protocol (MCP) Toolbox for Databases 用作“指挥”。它充当代理和 AlloyDB 之间的标准化中间件。通过定义 tools.yaml 配置,工具箱会自动将复杂的数据库操作公开为简洁的可执行工具(如 find-bus-schedules and routesquery-schedules for specific routes),并执行自主操作(如 book-ticket)。这样一来,您就无需在代理逻辑中手动进行连接池化或编写样板 SQL。

安装 Toolbox 服务器

在 Cloud Shell 终端中,创建一个文件夹来保存新的工具 YAML 文件和工具箱二进制文件:

mkdir cymbal-bus-toolbox

cd cymbal-bus-toolbox

在该新文件夹中,运行以下一组命令:

# see releases page for other versions
export VERSION=0.27.0
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox

接下来,在 Cloud Shell 编辑器中导航到该新文件夹,然后在其中创建 tools.yaml 文件,并将此代码库文件的内容复制到 tools.yaml 文件中。

... (Refer to entire file in the repo)

tools:

   find-bus-schedules:
    kind: postgres-sql
    source: alloydb
    description: Find all available bus schedules.
    statement: |
      SELECT CAST(trip_id AS TEXT) trip_id, departure_time, arrival_time, ticket_price, available_seats , origin_city, destination_city 
      FROM bus_schedules;

   query-schedules:
    kind: postgres-sql
    source: alloydb
    description: Find available bus schedules between an origin and destination city.
    parameters:
      - name: origin
        type: string
        description: The departure city name.
      - name: destination
        type: string
        description: The arrival city name.
    statement: |
      SELECT CAST(trip_id AS TEXT) trip_id, departure_time, arrival_time, ticket_price, available_seats 
      FROM bus_schedules 
      WHERE lower(origin_city) = lower($1) 
        AND lower(destination_city) = lower($2) 
        AND available_seats > 0 
      ORDER BY departure_time ASC 
      LIMIT 5;

   book-ticket:
    kind: postgres-sql
    source: alloydb
    description: Books a ticket for a specific trip, decrementing available seats and generating a confirmed booking record.
    parameters:
      - name: trip_id
        type: string
        description: The UUID of the trip schedule to book.
      - name: passenger_name
        type: string
        description: Name or ID of the passenger (Bound securely via backend or AuthToken).
        authServices:
          - name: google_auth
            field: sub
    statement: |
      WITH updated_schedule AS (
          UPDATE bus_schedules 
          SET available_seats = available_seats - 1 
          WHERE trip_id = CAST($1 AS UUID) AND available_seats > 0
          RETURNING trip_id
      )
      INSERT INTO bookings (trip_id, passenger_id)
      SELECT trip_id, $2 
      FROM updated_schedule
      RETURNING CAST(booking_id as TEXT) as booking_id, trip_id, passenger_id, status, booking_time;

   search-policies:
    kind: postgres-sql
    source: alloydb
    description: Semantic search for transit policies regarding luggage, pets, refunds, and general rules.
    parameters:
      - name: search_query
        type: string
        description: The user's question about transit policies to be embedded and searched.
    statement: |
      SELECT category, policy_text 
      FROM transit_policies 
      ORDER BY policy_embedding <=> CAST(embedding('text-embedding-005', $1) AS vector(768))
      LIMIT 2;

注意

  1. 在 tools.yaml 设置中,请勿忘记在 alloydb 源配置中添加 ipType: "private"
  2. 另请注意,在 authServices 配置的 clientId 参数中包含 MCP Toolbox 服务网址。您可能只有在初始部署完成后才能获得该链接 - 因此,您需要运行两次部署步骤,以确保经过身份验证的工具使用情形正常运行。
  3. 如果您的 AlloyDB 连接设置为私密,则以下用于在本地测试工具箱的选项将无法正常运行,您必须将其设置为公开才能在本地进行测试,或者使用代理进行连接。但不必担心。在本例中,我们将直接将其部署到 Cloud Run,然后进行测试。

如需在本地服务器中测试 tools.yaml 文件,请执行以下操作:

./toolbox --tools-file "tools.yaml"

您也可以在界面中进行测试:

./toolbox --ui

接下来,我们按照以下步骤在 Cloud Run 中部署该应用。

Cloud Run 部署

  1. 设置 PROJECT_ID 环境变量:
export PROJECT_ID="my-project-id"
  1. 初始化 gcloud CLI:
gcloud init
gcloud config set project $PROJECT_ID
  1. 您必须启用以下 API:
gcloud services enable run.googleapis.com \
                       cloudbuild.googleapis.com \
                       artifactregistry.googleapis.com \
                       iam.googleapis.com \
                       secretmanager.googleapis.com
  1. 如果您还没有后端服务账号,请创建一个:
gcloud iam service-accounts create toolbox-identity
  1. 授予使用 Secret Manager 的权限:
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member serviceAccount:toolbox-identity@$PROJECT_ID.iam.gserviceaccount.com \
    --role roles/secretmanager.secretAccessor
  1. 向服务账号授予特定于 AlloyDB 源的其他权限(roles/alloydb.client 和 roles/serviceusage.serviceUsageConsumer)
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member serviceAccount:toolbox-identity@$PROJECT_ID.iam.gserviceaccount.com \
    --role roles/alloydb.client


gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member serviceAccount:toolbox-identity@$PROJECT_ID.iam.gserviceaccount.com \
    --role roles/serviceusage.serviceUsageConsumer
  1. 将 tools.yaml 上传为 Secret:
gcloud secrets create tools-cymbal-transit --data-file=tools.yaml
  1. 如果您已有密文,并想更新密文版本,请执行以下操作:
gcloud secrets versions add tools-cymbal-transit --data-file=tools.yaml
  1. 为要用于 Cloud Run 的容器映像设置环境变量:
export IMAGE=us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:latest
  1. 使用以下命令将 Toolbox 部署到 Cloud Run:

如果您已在 AlloyDB 实例中启用公开访问权限,请按照以下命令将应用部署到 Cloud Run:

gcloud run deploy toolbox-cymbal-transit \
    --image $IMAGE \
    --service-account toolbox-identity \
    --region us-central1 \
    --set-secrets "/app/tools.yaml=tools-cymbal-transit:latest" \
    --args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080" \
    --allow-unauthenticated

如果您使用的是 VPC 网络,请使用以下命令:

gcloud run deploy toolbox-cymbal-transit \
    --image $IMAGE \
    --service-account toolbox-identity \
    --region us-central1 \
    --set-secrets "/app/tools.yaml=tools-cymbal-transit:latest" \
    --args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080" \
    --network <<YOUR_NETWORK_NAME>> \
    --subnet <<YOUR_SUBNET_NAME>> \
    --allow-unauthenticated

注意:部署完成后,前往 Cloud Run 服务列表 ,确保在该服务的“安全”标签页中,已选择“允许公开访问”。

6. 代理应用设置

将此代码库克隆到您的项目中,然后我们来逐步了解一下。

GitHub 仓库

如需克隆此项目,请在 Cloud Shell 终端中(在根目录中或从您要创建此项目的任何位置)运行以下命令:

git clone https://github.com/googleapis/mcp-toolbox-sdk-java

上述命令实际上会克隆整个 mcp-toolbox-sdk-java。我们只需要其中的示例项目。因此,请在代码库中进入项目的根目录:

cd mcp-toolbox-sdk-java/demo-applications/cymbal-transit
  1. 这应该会创建项目,您可以在 Cloud Shell 编辑器中验证这一点。

a494664032904c77.png

  1. 打开 CymbalTransitController.java 并设置环境变量:
  2. GCP_PROJECT_ID
  3. GCP_REGION
  4. GEMINI_MODEL_NAME
  5. MCP_TOOLBOX_URL

或者(仅用于开发目的),您也可以替换相应的回退值占位符。

7. 代码演示

CymbalTransitController 充当 Cloud Run 服务的入口点。它负责管理对话流程,并确保代理可以访问用户的当前请求。

该实现遵循分层架构,可分离 AI 编排、工具桥接和低级 MCP 通信。

1. AI 智能体配置 (AgentConfiguration)

此类使用 Spring 的 @Configuration 来启动 AI 组件。它会初始化 VertexAiGeminiChatModel 并将其绑定到我们的代理接口。

@Bean
ChatLanguageModel geminiChatModel() {
    return VertexAiGeminiChatModel.builder()
        .project(projectId)
        .location(region)
        .modelName(modelName)
        .build();
}

@Bean
TransitAgent transitAgent(ChatLanguageModel chatLanguageModel, TransitAgentTools tools) {
    return AiServices.builder(TransitAgent.class)
        .chatLanguageModel(chatLanguageModel)
        .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(20))
        .tools(tools) 
        .build();
}

意义: AiServices 将接口绑定到 LLM。MessageWindowChatMemory 可确保智能体在单个会话中记住用户偏好设置(例如之前提到的宠物背架),最多可记住 20 条消息。

2. AI 智能体界面 (TransitAgent)

@SystemMessage 注释定义了“角色”和操作限制,特别是路由策略

@SystemMessage({
    "You are the Cymbal Transit Concierge.",
    "CRITICAL INSTRUCTION: On your very first interaction, you MUST use the 'findAllSchedules' tool to fetch and memorize the broad bus routes.",
    "ONLY if the user asks a specifically narrowed-down question... should you route to the specific tools like 'querySchedules', 'bookTicket', 'searchPolicies'.",
    "Don't show any asterisks while listing results. Keep it formatted and numbered or bulleted."
})
String chat(@MemoryId String sessionId, @UserMessage String userMessage);

重要性:此策略可最大限度地缩短延迟时间。通过先提取广泛的数据,代理可以使用其内部上下文回答一般性路由问题,而无需进行冗余的后端调用。

3. 工具箱 Bridge (TransitAgentTools)

此服务充当代理的“手”,将 LangChain4j 工具调用转换为执行逻辑。

@Tool("Fetches the initial, broad dataset of all available bus schedules and routes.")
public String findAllSchedules() {
    return mcpService.findAllSchedules().join();
}


@Tool("Book a ticket for a passenger using a specific trip ID.")
public String bookTicket(String tripId, String passengerName) {
    return mcpService.bookTicket(tripId, passengerName).join();
}

同步执行:虽然 MCP 调用是异步的(返回 CompletableFuture),但 LLM 需要结果才能继续其“思考”过程。我们使用 .join() 将同步结果返回给代理。

4. MCP Toolbox 服务 (McpToolboxService)

这是使用 MCP Toolbox Java SDK 与 AlloyDB 后端交互的通信层。

// Identity Management: Fetching OIDC ID Token for Auth
GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
this.idToken = ((IdTokenProvider) credentials)
    .idTokenWithAudience(targetUrl, Collections.emptyList())
    .getTokenValue();

// Dynamic Invocation: Executing a tool by name
public CompletableFuture<String> findAllSchedules() {
    return mcpClient.invokeTool("find-bus-schedules", Collections.emptyMap()).thenApply(result -> {
        return result.content().stream()
            .map(content -> content.text())
            .collect(Collectors.joining(", ", "[", "]"));
    });
}

重要性: McpToolboxClient 可处理 JSON-RPC 通信的繁重任务。bookTicket 方法专门展示了 SDK 动态绑定复杂参数的能力。

5. REST 控制器 (TransitAgentController)

由于 LangChain4j 管理状态和逻辑,因此最终端点得到了大幅简化。

@PostMapping("/chat")
public ResponseEntity<String> handleUserChat(@RequestBody String userMessage, HttpSession session) {
    String sessionId = session.getId();
    String agentResponse = transitAgent.chat(sessionId, userMessage);
    return ResponseEntity.ok(agentResponse);
}

重要性:通过将 HttpSession ID 映射到 @MemoryId,我们可以确保不同用户的旅行计划不会混淆,同时保持控制器代码的简洁性和可读性。

8. MCP Toolbox:重要性和 Java SDK

什么是 MCP?

您可以将 Model Context Protocol (MCP) 视为 AI 的通用翻译器。MCP 旨在规范 AI 模型与外部工具和数据集的连接方式,它使用安全、通用的协议取代了自定义的碎片化集成脚本。无论您的代理需要执行事务性 SQL 查询、搜索数千份政策文档,还是触发 REST API,MCP 都能提供单一的统一接口。

MCP Toolbox for Databases

工程团队正在超越简单的聊天机器人,构建可直接与关键任务数据库交互的智能体系统。不过,构建这些企业智能体往往意味着会遇到自定义粘合代码、脆弱的 API 和复杂数据库逻辑的集成障碍。

为了用安全、统一的控制平面取代这些硬编码的瓶颈,我们很高兴地宣布推出适用于数据库的 Model Context Protocol (MCP) Toolbox 的 Java SDK。此版本为全球应用最广泛的企业生态系统带来了出色的类型安全代理编排功能。Java 成熟的架构专为满足这些严苛要求而打造,可提供高并发性、严格的事务完整性和强大的状态管理功能,从而在生产环境中安全地扩展关键任务 AI 代理。

为什么要使用 Java SDK?

借助 MCP Toolbox Java SDK,Java 开发者可以:

  1. 使用工具:连接到 MCP 服务器(例如 AlloyDB 的 MCP Toolbox),并自动将其功能转换为 LangChain4j 可理解的 Java 方法。
  2. 类型安全:利用 Java 的强类型功能来处理工具参数,从而减少工具调用中的运行时“幻觉”错误。
  3. 企业就绪状态:可轻松与 Spring Boot、Quarkus、Micronaut 等集成。
  4. 轻松连接:避免编写样板 JSON-RPC 代码。
  5. 标准化身份验证:对 Google Cloud OIDC 令牌的原生支持可确保安全地执行工具。

以及更多

依赖项:pom.xml 配置

将以下依赖项添加到 Maven 项目,以纳入最新的 MCP Toolbox Java SDK:

   <dependency>
        <groupId>com.google.cloud.mcp</groupId>
        <artifactId>mcp-toolbox-sdk-java</artifactId>
        <version>0.2.0</version>
    </dependency>

将以下依赖项添加到 Maven 项目中,以纳入 LangChain4j 工件:

     <!-- LangChain4j Core & Gemini -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
        <version>0.35.0</version>
    </dependency>

就是这样!我们已成功克隆项目,并详细了解了代理、MCP Toolbox Java SDK 和上下文。

9. 在本地运行

如需在您的机器上测试智能体,您需要将其指向已部署的 MCP Toolbox 服务器。

  1. 设置环境变量:
export GCP_PROJECT_ID="<<YOUR_PROJECT_ID>>"
export GCP_REGION="us-central1"
export GEMINI_MODEL_NAME="gemini-2.5-flash"
export MCP_TOOLBOX_URL="<<YOUR_TOOLBOX_ENDPOINT_URL>>/mcp"
  1. 使用 Maven 运行:
mvn compile

mvn spring-boot:run

这应该会在本地启动代理,您应该能够对其进行测试。

10. 我们将其部署到 Cloud Run

通过在克隆项目的 Cloud Shell 终端中运行以下命令,将其部署到 Cloud Run 上,确保您位于项目的根文件夹中

如果您不在当前项目的根文件夹中,请在 Cloud Shell 终端中运行以下命令:

cd cymbal-transit

如果您已位于 cymbal-transit 根目录下,请运行以下命令,直接在 Cloud Run 上部署应用:

gcloud run deploy cymbal-transit --source . --set-env-vars GCP_PROJECT_ID=<<YOUR_PROJECT_ID>>,GCP_REGION=us-central1,GEMINI_MODEL_NAME=gemini-2.5-flash,MCP_TOOLBOX_URL=<<YOUR_MCP_TOOLBOX_URL>> --allow-unauthenticated

替换占位符 <<YOUR_PROJECT>> and <<YOUR_MCP_TOOLBOX_URL>> 的值

命令运行完毕后,系统会输出服务网址。复制。

向 Cloud Run 服务账号授予 AlloyDB Client 角色。这样一来,您的无服务器应用就可以安全地通过隧道连接到数据库。

在 Cloud Shell 终端中运行以下命令:

# 1. Get your Project ID and Project Number
PROJECT_ID=$(gcloud config get-value project)
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")

# 2. Grant the AlloyDB Client role
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
--role="roles/alloydb.client"

注意:部署完成后,前往 Cloud Run 服务列表 ,确保在该服务的“安全”标签页中,已选择“允许公开访问”。

现在,使用服务网址(您之前复制的 Cloud Run 端点)测试应用。

注意:如果您遇到服务问题,并且该问题将内存不足列为原因,请尝试将分配的内存限制增加到 1 GiB 以进行测试。

11. 演示

向代理提出以下问题:“我需要明天早上从纽约前往波士顿。我可以带我的金毛寻回犬来吗?”观察代理如何:

  1. 搜索大型犬的政策。
  2. 查找特定时间表。
  3. 总结具有指定 Trip ID 的最快行程。
  4. 如果您跟进该操作请求,系统还会预订机票。

aa0408a81074d0fc.png

12. 清理

完成本实验后,请务必删除 AlloyDB 集群和实例。

它应清理集群及其实例。

13. 恭喜

您已成功构建一个复杂的基于 Java 的公交代理。通过利用 LangChain4j 进行编排和 MCP Toolbox Java SDK 进行数据连接,您创建了一个可以在代理、工具和数据源之间进行推理的系统。如果您想开始使用 MCP Toolbox for Databases 在多个数据库(甚至跨平台)中编排智能体应用,请立即开始使用 Java SDK点击此处可查看有关该库的更详细信息。如果您想免费以自己的节奏在讲师的指导下动手构建更多此类应用,请访问 https://codevipassana.dev 注册 Code Vipassana!