手撕AI Agent框架!多智能体+MCP+A2A全链路实战(DeepSeek可跑)

最近在AI Agent开发中踩了不少坑,特别是多智能体协作的场景下,不同框架的兼容性和部署复杂度让人头疼。本文将从零开始构建一个完整的餐厅多智能体系统,涵盖MCP工具集成、A2A协议通信、智能体部署等核心环节,所有代码都兼容DeepSeek模型,可直接运行。

1. AI Agent技术栈选型与架构设计

1.1 为什么需要多智能体架构?

在真实业务场景中,单个AI智能体往往难以胜任复杂任务。比如餐厅服务场景,需要菜单查询、预订管理、客户服务等多个专业能力。多智能体架构的优势在于:

  • 职责分离 :每个智能体专注特定领域,便于维护和迭代
  • 独立部署 :不同智能体可以有独立的更新周期和部署策略
  • 弹性扩展 :可根据业务负载单独扩缩容特定功能模块
  • 技术异构 :不同智能体可以采用最适合的技术栈

1.2 核心技术组件介绍

MCP(Model Context Protocol) MCP是连接AI智能体与工具数据的标准协议,让智能体能够安全地访问外部系统和数据源。在我们的餐厅场景中,MCP用于菜单数据的查询和管理。

A2A(Agent-to-Agent)协议
A2A解决了智能体间的通信问题,标准化了智能体发现、能力描述和任务协作的机制。与MCP主要区别在于:

  • MCP:智能体↔工具(无状态函数调用)
  • A2A:智能体↔智能体(有状态多轮对话)

ADK(Agent Development Kit) Google开源的AI智能体开发框架,提供统一的开发模式和部署工具。

1.3 系统架构设计

我们的多智能体餐厅系统包含以下组件:

用户请求
    ↓
餐厅主智能体(编排器)
    ├── MCP工具箱(菜单查询)
    └── A2A预订智能体(预订管理)
        ↓
    Agent Runtime(托管服务)

主智能体负责请求路由:菜单查询走MCP协议,预订操作走A2A协议。

2. 开发环境准备与依赖配置

2.1 基础环境要求

# 检查Python版本
python --version  # 需要Python 3.9+
uv --version     # 包管理工具

# 创建项目目录
mkdir ai-agent-restaurant && cd ai-agent-restaurant

2.2 项目依赖配置

创建 pyproject.toml 文件:

[project]
name = "ai-agent-restaurant"
version = "0.1.0"
description = "Multi-agent restaurant system with MCP and A2A"
requires-python = ">=3.9"

dependencies = [
    "google-cloud-aiplatform[agent_engines,adk]>=1.149.0",
    "a2a-sdk>=0.3.26",
    "google-adk>=1.29.0",
    "httpx>=0.25.0",
    "pydantic>=2.0.0",
    "cloudpickle>=2.0.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

创建环境配置文件 .env.example

# 项目配置
GOOGLE_CLOUD_PROJECT=your-project-id
GOOGLE_CLOUD_LOCATION=us-central1
REGION=us-central1

# 服务配置
TOOLBOX_URL=http://127.0.0.1:5000
RESERVATION_AGENT_CARD_URL=

# 部署配置
STAGING_BUCKET=your-project-adk-a2a-agent-runtime

2.3 初始化项目结构

# 创建项目目录结构
mkdir -p {reservation_agent,restaurant_agent,scripts,logs,tools}

# 初始化Python环境
uv sync

项目最终结构如下:

ai-agent-restaurant/
├── reservation_agent/     # 预订智能体
│   ├── agent.py
│   ├── a2a_config.py
│   ├── executor.py
│   └── __init__.py
├── restaurant_agent/      # 餐厅主智能体
│   ├── agent.py
│   └── __init__.py
├── scripts/               # 部署和测试脚本
├── tools/                 # MCP工具配置
├── logs/                  # 日志文件
└── pyproject.toml

3. 构建预订智能体(A2A服务端)

3.1 预订智能体核心逻辑

创建 reservation_agent/agent.py

import os
from functools import cached_property
from typing import Any
from google.adk.agents import LlmAgent
from google.adk.models.google_llm import Gemini
from google.adk.tools import ToolContext
from google.genai import Client, types

# 应用级状态前缀,确保预订数据在所有会话间持久化
STATE_PREFIX = "app:reservation:"

class DeepSeekCompatibleGemini(Gemini):
    """兼容DeepSeek的Gemini封装类"""
    
    @cached_property
    def api_client(self) -> Client:
        project = os.getenv("GOOGLE_CLOUD_PROJECT")
        return Client(
            project=project,
            location="global",
            http_options=types.HttpOptions(
                headers=self._tracking_headers(),
                retry_options=self.retry_options,
            ),
        )

def create_reservation(
    phone_number: str,
    name: str,
    party_size: int,
    date: str,
    time: str,
    tool_context: ToolContext,
) -> dict:
    """创建新的餐厅预订"""
    reservation = {
        "name": name,
        "party_size": party_size,
        "date": date,
        "time": time,
        "status": "confirmed",
    }
    
    # 使用电话号码作为键存储预订信息
    tool_context.state[f"{STATE_PREFIX}{phone_number}"] = reservation
    
    return {
        "status": "confirmed",
        "message": f"Reservation created for {name}, party of {party_size} on {date} at {time}. Phone: {phone_number}.",
    }

def check_reservation(phone_number: str, tool_context: ToolContext) -> dict:
    """通过电话号码查询预订"""
    reservation = tool_context.state.get(f"{STATE_PREFIX}{phone_number}")
    
    if reservation:
        return {"found": True, "reservation": reservation}
    
    return {"found": False, "message": f"No reservation found for {phone_number}."}

def cancel_reservation(phone_number: str, tool_context: ToolContext) -> dict:
    """取消现有预订"""
    key = f"{STATE_PREFIX}{phone_number}"
    reservation = tool_context.state.get(key)
    
    if not reservation:
        return {
            "success": False,
            "message": f"No reservation found for {phone_number}.",
        }
    
    if reservation.get("status") == "cancelled":
        return {
            "success": False,
            "message": f"Reservation for {phone_number} is already cancelled.",
        }
    
    reservation["status"] = "cancelled"
    tool_context.state[key] = reservation
    
    return {
        "success": True,
        "message": f"Reservation for {reservation['name']} ({phone_number}) has been cancelled.",
    }

# 创建主智能体实例
root_agent = LlmAgent(
    name="reservation_agent",
    model=DeepSeekCompatibleGemini(model="gemini-3.5-flash"),
    instruction="""You are a friendly reservation assistant for "Foodie Finds" restaurant.
You help diners create, check, and cancel table reservations.

When a diner wants to make a reservation, collect these details:
- Name for the reservation
- Phone number (used as the reservation ID)
- Party size (number of guests)
- Date
- Time

Always confirm the details before creating the reservation.
When checking or cancelling, ask for the phone number if not provided.
Be concise and professional.""",
    tools=[create_reservation, check_reservation, cancel_reservation],
)

3.2 A2A智能体卡片配置

创建 reservation_agent/a2a_config.py

from a2a.types import AgentSkill
from vertexai.preview.reasoning_engines.templates.a2a import create_agent_card

# 定义预订技能
reservation_skill = AgentSkill(
    id="manage_reservations",
    name="Restaurant Reservations",
    description="Create, check, and cancel table reservations at Foodie Finds restaurant",
    tags=["reservations", "restaurant", "booking"],
    examples=[
        "Book a table for 4 on Friday at 7pm",
        "Check reservation for 555-0101",
        "Cancel my reservation, phone number 555-0101",
    ],
    input_modes=["text/plain"],
    output_modes=["text/plain"],
)

# 创建智能体卡片
agent_card = create_agent_card(
    agent_name="Reservation Agent",
    description="Handles restaurant table reservations — create, check, and cancel bookings for Foodie Finds restaurant.",
    skills=[reservation_skill],
)

3.3 A2A执行器实现

创建 reservation_agent/executor.py

import os
from typing import NoReturn
import vertexai
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
from a2a.server.tasks import TaskUpdater
from a2a.types import TaskState, TextPart, UnsupportedOperationError
from a2a.utils import new_agent_text_message
from a2a.utils.errors import ServerError
from google.adk.artifacts import InMemoryArtifactService
from google.adk.memory.in_memory_memory_service import InMemoryMemoryService
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService, VertexAiSessionService
from google.genai import types

from reservation_agent.agent import root_agent as reservation_agent

class ReservationAgentExecutor(AgentExecutor):
    """A2A协议与ADK预订智能体之间的桥梁"""
    
    def __init__(self) -> None:
        self.agent = None
        self.runner = None
    
    def _init_agent(self) -> None:
        if self.agent is not None:
            return
            
        self.agent = reservation_agent
        engine_id = os.environ.get("GOOGLE_CLOUD_AGENT_ENGINE_ID")
        
        # 根据环境选择会话服务
        if engine_id:
            # 生产环境:使用持久化会话
            project = os.environ.get("GOOGLE_CLOUD_PROJECT")
            location = os.environ.get("GOOGLE_CLOUD_LOCATION", "us-central1")
            vertexai.init(project=project, location=location)
            session_service = VertexAiSessionService(
                project=project, location=location, agent_engine_id=engine_id,
            )
            app_name = engine_id
        else:
            # 本地环境:使用内存会话
            session_service = InMemorySessionService()
            app_name = self.agent.name
        
        self.runner = Runner(
            app_name=app_name,
            agent=self.agent,
            artifact_service=InMemoryArtifactService(),
            session_service=session_service,
            memory_service=InMemoryMemoryService(),
        )
    
    async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
        if self.agent is None:
            self._init_agent()
        
        query = context.get_user_input()
        updater = TaskUpdater(event_queue, context.task_id, context.context_id)
        
        user_id = context.message.metadata.get("user_id", "a2a-user") if context.message.metadata else "a2a-user"
        
        if not context.current_task:
            await updater.submit()
        
        await updater.start_work()
        
        try:
            session = await self._get_or_create_session(context.context_id, user_id)
            content = types.Content(role="user", parts=[types.Part(text=query)])
            
            async for event in self.runner.run_async(
                session_id=session.id, user_id=user_id, new_message=content,
            ):
                if event.is_final_response():
                    parts = event.content.parts
                    answer = " ".join(p.text for p in parts if p.text) or "No response."
                    await updater.add_artifact([TextPart(text=answer)], name="answer")
                    await updater.complete()
                    break
                    
        except Exception as e:
            await updater.update_status(
                TaskState.failed, message=new_agent_text_message(f"Error: {e!s}"),
            )
            raise
    
    async def _get_or_create_session(self, context_id: str, user_id: str):
        app_name = self.runner.app_name
        
        if context_id:
            session = await self.runner.session_service.get_session(
                app_name=app_name, session_id=context_id, user_id=user_id,
            )
            if session:
                return session
        
        session = await self.runner.session_service.create_session(
            app_name=app_name, user_id=user_id, session_id=context_id,
        )
        return session
    
    async def cancel(self, context: RequestContext, event_queue: EventQueue) -> NoReturn:
        raise ServerError(error=UnsupportedOperationError())

4. 本地测试A2A智能体

4.1 创建本地测试脚本

创建 scripts/test_a2a_agent_local.py

import asyncio
import json
import os
from pprint import pprint
from dotenv import load_dotenv
from starlette.requests import Request
from vertexai.preview.reasoning_engines import A2aAgent

from reservation_agent.a2a_config import agent_card
from reservation_agent.executor import ReservationAgentExecutor

load_dotenv()

def build_post_request(data: dict = None, path_params: dict = None) -> Request:
    """构建模拟POST请求"""
    scope = {
        "type": "http", 
        "http_version": "1.1", 
        "headers": [(b"content-type", b"application/json")], 
        "app": None
    }
    
    if path_params:
        scope["path_params"] = path_params
    
    async def receive():
        byte_data = json.dumps(data).encode("utf-8") if data else b""
        return {"type": "http.request", "body": byte_data, "more_body": False}
    
    return Request(scope, receive)

async def wait_for_task(a2a_agent, task_id, max_retries=30):
    """轮询任务直到完成"""
    for _ in range(max_retries):
        request = build_post_request()
        result = await a2a_agent.on_get_task(request=request, context=None)
        state = result.get("status", {}).get("state", "")
        if state in ["completed", "failed"]:
            return result
        await asyncio.sleep(1)
    return result

async def main():
    # 创建本地A2A智能体
    a2a_agent = A2aAgent(
        agent_card=agent_card, 
        agent_executor_builder=ReservationAgentExecutor
    )
    a2a_agent.set_up()
    
    print("=" * 50)
    print("1. 测试智能体卡片检索...")
    print("=" * 50)
    
    # 测试智能体卡片
    request = build_post_request()
    card_response = await a2a_agent.handle_authenticated_agent_card(request=request, context=None)
    print(f"智能体名称: {card_response.get('name')}")
    print(f"可用技能: {[s.get('name') for s in card_response.get('skills', [])]}")
    
    # 测试预订创建
    print("\n" + "=" * 50)
    print("2. 测试预订创建...")
    print("=" * 50)
    
    message_data = {
        "message": {
            "messageId": f"msg-test-001",
            "content": [{"text": "Book a table for 2 on Saturday at 6pm. Name: Bob, Phone: 555-0202"}],
            "role": "ROLE_USER",
        },
    }
    
    request = build_post_request(message_data)
    response = await a2a_agent.on_message_send(request=request, context=None)
    task_id = response["task"]["id"]
    context_id = response["task"].get("contextId")
    
    print(f"任务ID: {task_id}")
    
    # 等待任务完成
    result = await wait_for_task(a2a_agent, task_id)
    print(f"任务状态: {result.get('status', {}).get('state')}")
    
    if result.get("artifacts"):
        for artifact in result.get("artifacts", []):
            if artifact.get("parts") and "text" in artifact["parts"][0]:
                print(f"智能体回复: {artifact['parts'][0]['text']}")
    
    print("\n" + "=" * 50)
    print("本地A2A测试完成!")
    print("=" * 50)

if __name__ == "__main__":
    asyncio.run(main())

4.2 运行本地测试

# 设置环境变量
source .env

# 运行测试
PYTHONPATH=. uv run python scripts/test_a2a_agent_local.py

预期输出:

==================================================
1. 测试智能体卡片检索...
==================================================
智能体名称: Reservation Agent
可用技能: ['Restaurant Reservations']

==================================================
2. 测试预订创建...
==================================================
任务ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
任务状态: completed
智能体回复: Your reservation for Bob, party of 2, on Saturday at 6:00 PM has been confirmed.

5. 部署A2A智能体到Agent Runtime

5.1 创建部署脚本

创建 scripts/deploy_a2a_agent_runtime.py

import os
from pathlib import Path
import vertexai
from dotenv import load_dotenv
from google.genai import types
from vertexai.preview.reasoning_engines import A2aAgent

from reservation_agent.a2a_config import agent_card
from reservation_agent.executor import ReservationAgentExecutor

load_dotenv()

def main():
    PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
    REGION = os.environ["REGION"]
    STAGING_BUCKET = os.environ.get("STAGING_BUCKET", f"{PROJECT_ID}-adk-a2a-agent-runtime")
    BUCKET_URI = f"gs://{STAGING_BUCKET}"
    
    # 初始化A2A智能体
    a2a_agent = A2aAgent(
        agent_card=agent_card,
        agent_executor_builder=ReservationAgentExecutor,
    )
    
    # 初始化Vertex AI
    vertexai.init(project=PROJECT_ID, location=REGION, staging_bucket=BUCKET_URI)
    
    client = vertexai.Client(
        project=PROJECT_ID,
        location=REGION,
        http_options=types.HttpOptions(api_version="v1beta1"),
    )
    
    print("正在部署预订智能体到Agent Runtime...")
    print("这可能需要3-5分钟...")
    
    # 部署到Agent Runtime
    remote_agent = client.agent_engines.create(
        agent=a2a_agent,
        config={
            "display_name": agent_card.name,
            "description": agent_card.description,
            "requirements": [
                "google-cloud-aiplatform[agent_engines,adk]==1.149.0",
                "a2a-sdk==0.3.26",
                "google-adk==1.29.0",
                "cloudpickle>=2.0.0",
                "pydantic>=2.0.0"
            ],
            "extra_packages": ["./reservation_agent"],
            "http_options": {"api_version": "v1beta1"},
            "staging_bucket": BUCKET_URI,
        },
    )
    
    resource_name = remote_agent.api_resource.name
    print(f"\n部署完成!")
    print(f"资源名称: {resource_name}")
    
    # 保存资源名称到环境文件
    env_path = Path(".env")
    lines = env_path.read_text().splitlines() if env_path.exists() else []
    lines = [l for l in lines if not l.startswith("RESERVATION_AGENT_RESOURCE_NAME=")]
    lines.append(f"RESERVATION_AGENT_RESOURCE_NAME={resource_name}")
    env_path.write_text("\n".join(lines) + "\n")
    
    print("已将RESERVATION_AGENT_RESOURCE_NAME写入.env文件")

if __name__ == "__main__":
    main()

5.2 执行部署

# 创建存储桶用于暂存
STAGING_BUCKET="${GOOGLE_CLOUD_PROJECT}-adk-a2a-agent-runtime"
gsutil mb -l $REGION -p $GOOGLE_CLOUD_PROJECT gs://$STAGING_BUCKET 2>/dev/null || echo "存储桶已存在"
echo "STAGING_BUCKET=$STAGING_BUCKET" >> .env

# 运行部署
source .env
PYTHONPATH=. uv run python scripts/deploy_a2a_agent_runtime.py

5.3 验证部署

创建测试脚本 scripts/test_deployed_agent.py

import asyncio
import os
import time
import vertexai
from a2a.types import TaskState
from dotenv import load_dotenv
from google.genai import types

load_dotenv()

async def main():
    PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
    REGION = os.environ["REGION"]
    RESOURCE_NAME = os.environ["RESERVATION_AGENT_RESOURCE_NAME"]
    
    vertexai.init(project=PROJECT_ID, location=REGION)
    client = vertexai.Client(
        project=PROJECT_ID, 
        location=REGION,
        http_options=types.HttpOptions(api_version="v1beta1"),
    )
    
    # 获取已部署的智能体
    agent = client.agent_engines.get(name=RESOURCE_NAME)
    
    print("=" * 50)
    print("测试已部署的A2A智能体...")
    print("=" * 50)
    
    # 发送测试消息
    message_data = {
        "messageId": "msg-deploy-test-001",
        "role": "user",
        "parts": [{"kind": "text", "text": "Book a table for 3 on Sunday at noon. Name: Carol, Phone: 555-0303"}],
    }
    
    response = await agent.on_message_send(**message_data)
    
    # 处理响应
    task_object = None
    for chunk in response:
        if isinstance(chunk, tuple) and len(chunk) > 0 and hasattr(chunk[0], "id"):
            task_object = chunk[0]
            break
    
    if task_object:
        print(f"任务ID: {task_object.id}")
        print(f"初始状态: {task_object.status.state}")
        
        # 等待任务完成
        for _ in range(30):
            try:
                result = await agent.on_get_task(id=task_object.id)
                if result.status.state in [TaskState.completed, TaskState.failed]:
                    break
            except Exception:
                pass
            time.sleep(1)
        
        print(f"最终状态: {result.status.state}")
        
        if result.artifacts:
            for artifact in result.artifacts:
                if artifact.parts and hasattr(artifact.parts[0], "text"):
                    print(f"智能体回复: {artifact.parts[0].text}")

if __name__ == "__main__":
    asyncio.run(main())

6. 构建餐厅主智能体(MCP + A2A集成)

6.1 主智能体实现

创建 restaurant_agent/agent.py

import os
import httpx
from google.adk.agents import LlmAgent
from google.adk.agents.remote_a2a_agent import RemoteA2aAgent
from google.auth import default
from google.auth.transport.requests import Request as AuthRequest

# MCP工具箱集成(模拟)
class MockToolboxToolset:
    """模拟MCP工具箱用于菜单查询"""
    
    def __init__(self, toolbox_url: str):
        self.toolbox_url = toolbox_url
    
    async def search_menu(self, query: str) -> dict:
        """模拟菜单搜索"""
        # 在实际项目中这里会调用真实的MCP服务
        menu_items = [
            {"name": "Margherita Pizza", "price": 12.99, "category": "Italian"},
            {"name": "Caesar Salad", "price": 8.99, "category": "Salads"},
            {"name": "Spaghetti Carbonara", "price": 14.99, "category": "Italian"},
        ]
        
        return {
            "results": [item for item in menu_items if query.lower() in item["name"].lower() or query.lower() in item["category"].lower()],
            "count": len(menu_items)
        }

# Google Cloud认证处理
class GoogleCloudAuth(httpx.Auth):
    """自动刷新Google Cloud访问令牌"""
    
    def __init__(self):
        self.credentials, _ = default(
            scopes=["https://www.googleapis.com/auth/cloud-platform"]
        )
    
    def auth_flow(self, request):
        if not self.credentials.valid:
            self.credentials.refresh(AuthRequest())
        request.headers["Authorization"] = f"Bearer {self.credentials.token}"
        yield request

# 配置环境变量
TOOLBOX_URL = os.environ.get("TOOLBOX_URL", "http://127.0.0.1:5000")
RESERVATION_AGENT_CARD_URL = os.environ.get("RESERVATION_AGENT_CARD_URL", "")

# 初始化工具箱
toolbox = MockToolboxToolset(TOOLBOX_URL)

# 创建远程A2A智能体(预订服务)
reservation_remote_agent = RemoteA2aAgent(
    name="reservation_agent",
    description="Handles restaurant table reservations — create, check, and cancel bookings.",
    agent_card=RESERVATION_AGENT_CARD_URL,
    httpx_client=httpx.AsyncClient(auth=GoogleCloudAuth(), timeout=60),
)

# 创建主智能体
root_agent = LlmAgent(
    name="restaurant_agent",
    model="gemini-3.5-flash",
    instruction="""You are a friendly and knowledgeable concierge at "Foodie Finds" restaurant. 

核心职责:
- 通过MCP工具箱帮助顾客浏览菜单(按类别或菜系)
- 提供菜品详细信息(成分、价格、饮食信息)
- 根据顾客描述推荐菜品
- 将预订相关请求委托给专门的预订智能体

请求路由逻辑:
1. 菜单查询 → MCP工具箱
2. 预订操作 → A2A预订智能体

对话风格:专业、友好、简洁,确保顾客获得准确信息。""",
    tools=[toolbox.search_menu],  # 实际项目中会有更多MCP工具
    sub_agents=[reservation_remote_agent],
)

6.2 智能体卡片解析脚本

创建 scripts/resolve_agent_card_url.py

import asyncio
import os
from pathlib import Path
import vertexai
from dotenv import load_dotenv
from google.genai import types

load_dotenv()

async def main():
    PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
    REGION = os.environ["REGION"]
    RESOURCE_NAME = os.environ["RESERVATION_AGENT_RESOURCE_NAME"]
    
    vertexai.init(project=PROJECT_ID, location=REGION)
    client = vertexai.Client(
        project=PROJECT_ID, 
        location=REGION,
        http_options=types.HttpOptions(api_version="v1beta1"),
    )
    
    # 获取已部署的智能体
    agent = client.agent_engines.get(name=RESOURCE_NAME)
    
    # 获取智能体卡片
    card = await agent.handle_authenticated_agent_card()
    card_url = f"{card.url}/v1/card"
    
    print(f"智能体: {card.name}")
    print(f"卡片URL: {card_url}")
    
    # 更新环境配置文件
    for env_path in [Path("restaurant_agent/.env"), Path(".env")]:
        lines = env_path.read_text().splitlines() if env_path.exists() else []
        lines = [l for l in lines if not l.startswith("RESERVATION_AGENT_CARD_URL=")]
        lines.append(f"RESERVATION_AGENT_CARD_URL={card_url}")
        env_path.write_text("\n".join(lines) + "\n")
        print(f"已更新 {env_path}")

if __name__ == "__main__":
    asyncio.run(main())

7. 完整系统集成测试

7.1 集成测试脚本

创建 scripts/test_integrated_system.py

import asyncio
import os
from dotenv import load_dotenv
from restaurant_agent.agent import root_agent as restaurant_agent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.artifacts import InMemoryArtifactService
from google.adk.memory.in_memory_memory_service import InMemoryMemoryService
from google.genai import types

load_dotenv()

async def test_integrated_system():
    """测试完整的多智能体系统"""
    
    # 初始化餐厅智能体运行器
    runner = Runner(
        app_name="restaurant_test",
        agent=restaurant_agent,
        artifact_service=InMemoryArtifactService(),
        session_service=InMemorySessionService(),
        memory_service=InMemoryMemoryService(),
    )
    
    test_cases = [
        {
            "name": "菜单查询测试",
            "query": "What Italian dishes do you have?",
            "expected_keywords": ["pizza", "pasta", "Italian"]
        },
        {
            "name": "预订创建测试", 
            "query": "I want to book a table for 4 people this Friday at 7 PM",
            "expected_keywords": ["reservation", "book", "table"]
        },
        {
            "name": "预订查询测试",
            "query": "Can you check my reservation for phone number 555-0202?",
            "expected_keywords": ["reservation", "check", "found"]
        }
    ]
    
    for i, test_case in enumerate(test_cases):
        print(f"\n{'='*50}")
        print(f"测试 {i+1}: {test_case['name']}")
        print(f"{'='*50}")
        
        print(f"用户查询: {test_case['query']}")
        
        # 运行智能体
        session = await runner.session_service.create_session(
            app_name="restaurant_test", 
            user_id=f"test_user_{i}",
            session_id=f"test_session_{i}"
        )
        
        content = types.Content(role="user", parts=[types.Part(text=test_case['query'])])
        
        async for event in runner.run_async(
            session_id=session.id, 
            user_id=f"test_user_{i}", 
            new_message=content,
        ):
            if event.is_final_response():
                response_text = " ".join(p.text for p in event.content.parts if p.text)
                print(f"智能体回复: {response_text}")
                
                # 验证回复包含预期关键词
                keywords_found = [
                    keyword for keyword in test_case['expected_keywords'] 
                    if keyword.lower() in response_text.lower()
                ]
                print(f"找到关键词: {keywords_found}")
                break

async def main():
    print("开始集成系统测试...")
    await test_integrated_system()
    print("\n测试完成!")

if __name__ == "__main__":
    asyncio.run(main())

7.2 运行集成测试

# 解析智能体卡片URL
uv run python scripts/resolve_agent_card_url.py

# 运行集成测试
source .env
PYTHONPATH=. uv run python scripts/test_integrated_system.py

8. 生产环境部署与优化

8.1 Cloud Run部署配置

创建 scripts/deploy_to_cloud_run.py

import os
import subprocess
from dotenv import load_dotenv

load_dotenv()

def deploy_restaurant_agent():
    """部署餐厅主智能体到Cloud Run"""
    
    PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
    REGION = os.environ["REGION"]
    RESERVATION_AGENT_CARD_URL = os.environ["RESERVATION_AGENT_CARD_URL"]
    
    # 授予Cloud Run服务账号访问Agent Runtime的权限
    project_number_cmd = f"gcloud projects describe {PROJECT_ID} --format='value(projectNumber)'"
    project_number = subprocess.check_output(project_number_cmd, shell=True).decode().strip()
    
    # 设置IAM权限
    iam_cmd = f"""
    gcloud projects add-iam-policy-binding {PROJECT_ID} \
        --member="serviceAccount:{project_number}-compute@developer.gserviceaccount.com" \
        --role="roles/aiplatform.user"
    """
    
    subprocess.run(iam_cmd, shell=True, check=True)
    
    # 部署到Cloud Run
    deploy_cmd = f"""
    gcloud run deploy restaurant-agent \
        --source . \
        --region={REGION} \
        --allow-unauthenticated \
        --update-env-vars="RESERVATION_AGENT_CARD_URL={RESERVATION_AGENT_CARD_URL}" \
        --min-instances=0 \
        --max-instances=1 \
        --memory=1Gi \
        --port=8080
    """
    
    print("正在部署餐厅智能体到Cloud Run...")
    subprocess.run(deploy_cmd, shell=True, check=True)
    
    # 获取服务URL
    url_cmd = f"gcloud run services describe restaurant-agent --region={REGION} --format='value(status.url)'"
    service_url = subprocess.check_output(url_cmd, shell=True).decode().strip()
    
    print(f"部署完成! 服务URL: {service_url}")
    return service_url

if __name__ == "__main__":
    deploy_restaurant_agent()

8.2 性能优化配置

创建 optimization_config.yaml

# 智能体性能优化配置
optimization:
  # 会话管理
  session:
    timeout: 3600  # 会话超时时间(秒)
    max_size: 1000  # 最大会话数
    
  # 缓存配置
  cache:
    enabled: true
    ttl: 300  # 缓存存活时间
    
  # 并发控制
  concurrency:
    max_workers: 10
    queue_size: 100
    
  # 监控配置
  monitoring:
    enabled: true
    metrics:
      - response_time
      - error_rate
      - throughput

9. 常见问题与解决方案

9.

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐