{
  "metadata": {
    "id": "ch13",
    "title": "第13章：Agent与外部系统集成",
    "volume": "vol4",
    "volume_title": "高级篇",
    "word_count": 2135,
    "difficulty": "intermediate",
    "prerequisites": [
      "ch06"
    ],
    "key_concepts": [
      "集成的必要性与挑战",
      "为什么Agent需要集成外部系统",
      "核心挑战",
      "集成架构总览",
      "REST API集成",
      "HTTP客户端设计",
      "OAuth2集成",
      "分页处理",
      "数据库集成",
      "SQL查询Agent",
      "NoSQL集成",
      "消息队列与事件系统集成",
      "Kafka集成",
      "异步事件处理模式",
      "MCP协议集成"
    ],
    "learning_objectives": [],
    "estimated_tokens": 1281,
    "source_file": "vol4/ch13_Agent与外部系统集成.md"
  },
  "overview": "Agent 的价值不仅在于其推理能力，更在于它能与外部世界交互——调用API、查询数据库、发送消息、操作文件。一个孤立的 Agent 只是一个\"聊天机器人\"，而一个连接了外部系统的 Agent 才是真正的\"数字员工\"。本章将系统地讲解 Agent 与各类外部系统集成的方法、模式和最佳实践，涵盖 REST API、数据库、消息队列、MCP 协议和第三方 SaaS 等典型场景。",
  "sections": [
    {
      "id": "13.1",
      "title": "13.1 集成的必要性与挑战",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "13.1.1",
          "title": "13.1.1 为什么Agent需要集成外部系统",
          "content": "Agent 的本质是\"LLM大脑 + 工具手脚\"。没有外部工具的 Agent 就像一个聪明但没有手脚的学者——知识渊博但无法行动。\n\n\n典型的集成需求包括：\n- **数据获取**：从数据库、搜索引擎、文件系统中获取信息\n- **操作执行**：创建工单、发送邮件、更新记录\n- **通知推送**：将 Agent 的决策结果通知到 Slack/钉钉/飞书\n- **工作流协作**：与 CI/CD 流水线、审批流程集成"
        },
        {
          "id": "13.1.2",
          "title": "13.1.2 核心挑战",
          "content": "| 挑战 | 描述 | 典型解决方案 |\n|------|------|-------------|\n| API多样性 | 不同的认证方式、数据格式、错误码 | 统一适配层 |\n| 认证安全 | 凭证管理、Token刷新、权限控制 | Secret Manager + OAuth |\n| 数据格式 | JSON/XML/Protobuf，Schema差异 | 数据转换层 |\n| 错误处理 | 超时、限流、服务不可用 | 重试 + 熔断 + 降级 |\n| 幂等性 | 重复调用导致副作用 | 幂等设计 + 请求去重 |\n| 数据一致性 | 分布式事务 | 最终一致性 + 补偿 |"
        },
        {
          "id": "13.1.3",
          "title": "13.1.3 集成架构总览",
          "content": ""
        }
      ]
    },
    {
      "id": "13.2",
      "title": "13.2 REST API集成",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "13.2.1",
          "title": "13.2.1 HTTP客户端设计",
          "content": ""
        },
        {
          "id": "13.2.2",
          "title": "13.2.2 OAuth2集成",
          "content": ""
        },
        {
          "id": "13.2.3",
          "title": "13.2.3 分页处理",
          "content": ""
        }
      ]
    },
    {
      "id": "13.3",
      "title": "13.3 数据库集成",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "13.3.1",
          "title": "13.3.1 SQL查询Agent",
          "content": ""
        },
        {
          "id": "13.3.2",
          "title": "13.3.2 NoSQL集成",
          "content": ""
        }
      ]
    },
    {
      "id": "13.4",
      "title": "13.4 消息队列与事件系统集成",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "13.4.1",
          "title": "13.4.1 Kafka集成",
          "content": ""
        },
        {
          "id": "13.4.2",
          "title": "13.4.2 异步事件处理模式",
          "content": ""
        }
      ]
    },
    {
      "id": "13.5",
      "title": "13.5 MCP协议集成",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "13.5.1",
          "title": "13.5.1 MCP协议简介",
          "content": "MCP（Model Context Protocol）是 Anthropic 提出的开放协议，标准化了 LLM/AI 模型与外部工具、数据源的集成方式。它定义了统一的接口规范，让 Agent 可以以标准化的方式发现和使用工具。"
        },
        {
          "id": "13.5.2",
          "title": "13.5.2 MCP工具集成到Agent",
          "content": ""
        }
      ]
    },
    {
      "id": "13.6",
      "title": "13.6 第三方SaaS集成",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "13.6.1",
          "title": "13.6.1 Slack集成",
          "content": ""
        },
        {
          "id": "13.6.2",
          "title": "13.6.2 GitHub集成",
          "content": ""
        }
      ]
    },
    {
      "id": "13.7",
      "title": "13.7 集成模式与反模式",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "13.7.1",
          "title": "13.7.1 适配器模式（Adapter Pattern）",
          "content": ""
        },
        {
          "id": "13.7.2",
          "title": "13.7.2 熔断器模式",
          "content": ""
        },
        {
          "id": "13.7.3",
          "title": "13.7.3 限流器",
          "content": ""
        }
      ]
    },
    {
      "id": "13.8",
      "title": "13.8 安全考量",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "13.8.1",
          "title": "13.8.1 凭证管理",
          "content": ""
        },
        {
          "id": "13.8.2",
          "title": "13.8.2 输入验证与数据脱敏",
          "content": ""
        }
      ]
    },
    {
      "id": "最佳实践",
      "title": "最佳实践",
      "level": 2,
      "content": "1. **统一接口层**：所有外部集成通过统一的 Tool Adapter 暴露给 Agent，保持接口一致性\n2. **故障隔离**：每个集成都配置独立的超时、重试和熔断策略，防止级联故障\n3. **凭证零信任**：凭证通过 Secret Manager 管理，绝不硬编码在代码或配置文件中\n4. **幂等设计**：所有写操作设计为幂等的，支持安全重试\n5. **监控可观测**：每个集成调用记录延迟、成功率、错误类型等指标",
      "subsections": []
    },
    {
      "id": "常见陷阱",
      "title": "常见陷阱",
      "level": 2,
      "content": "1. **同步阻塞**：在 async Agent 中调用同步的第三方 SDK，阻塞事件循环。使用 `asyncio.to_thread()` 包装\n2. **超时缺失**：不设置HTTP超时，导致 Agent 在外部服务不可用时无限等待\n3. **凭证泄露**：在日志中打印请求体，泄露 API Key 或 Token。使用脱敏日志\n4. **忽略速率限制**：高频调用外部 API 导致被封禁。必须实现限流\n5. **过度耦合**：Agent 逻辑直接依赖特定 API 的响应格式。通过适配器解耦",
      "subsections": []
    },
    {
      "id": "小结",
      "title": "小结",
      "level": 2,
      "content": "外部系统集成是 Agent 从\"玩具\"走向\"生产力工具\"的关键一步。本章介绍了 REST API、数据库、消息队列、MCP 协议和第三方 SaaS 等典型集成场景的实现方法，以及适配器、熔断器、限流器等核心集成模式。在实际项目中，建议构建统一的集成层，将所有外部系统标准化为 Agent 工具，降低耦合、提升可靠性。",
      "subsections": []
    },
    {
      "id": "延伸阅读",
      "title": "延伸阅读",
      "level": 2,
      "content": "1. **MCP协议规范**: https://modelcontextprotocol.io/\n2. **论文**: \"Toolformer: Language Models Can Teach Themselves to Use Tools\"\n3. **书籍**: \"Microservices Patterns\" (Chris Richardson) — 服务集成的经典模式\n4. **OAuth 2.0 规范**: https://oauth.net/2/\n5. **GitHub API文档**: https://docs.github.com/en/rest",
      "subsections": []
    }
  ],
  "code_blocks": [
    {
      "id": "code-1",
      "language": "mermaid",
      "description": "Agent 的本质是\"LLM大脑 + 工具手脚\"。没有外部工具的 Agent 就像一个聪明但没有手脚的学者——知识渊博但无法行动。",
      "code": "graph TB\n    A[Agent核心] --> B[LLM推理引擎]\n    A --> C[工具层]\n    C --> D[REST API]\n    C --> E[数据库]\n    C --> F[消息队列]\n    C --> G[文件系统]\n    C --> H[SaaS服务]\n    C --> I[MCP协议]\n    C --> J[自定义服务]",
      "section_ref": "13.1.1",
      "runnable": false,
      "dependencies": []
    },
    {
      "id": "code-2",
      "language": "python",
      "description": "| 数据一致性 | 分布式事务 | 最终一致性 + 补偿 |",
      "code": "from dataclasses import dataclass, field\nfrom typing import Any, Protocol\nfrom abc import ABC, abstractmethod\nfrom enum import Enum\n\nclass IntegrationType(Enum):\n    REST_API = \"rest_api\"\n    DATABASE = \"database\"\n    MESSAGE_QUEUE = \"message_queue\"\n    MCP = \"mcp\"\n    SaaS = \"saas\"\n    FILE_SYSTEM = \"file_system\"\n\n@dataclass\nclass IntegrationConfig:\n    \"\"\"集成配置\"\"\"\n    name: str\n    type: IntegrationType\n    base_url: str | None = None\n    auth_type: str = \"none\"  # none, api_key, oauth2, basic\n    timeout: float = 30.0\n    retry_count: int = 3\n    rate_limit: int = 100  # 每分钟最大请求数\n\nclass IntegrationBase(ABC):\n    \"\"\"集成基类：定义统一接口\"\"\"\n    \n    def __init__(self, config: IntegrationConfig):\n        self.config = config\n        self._circuit_breaker = CircuitBreaker(\n            failure_threshold=5,\n            recovery_timeout=60\n        )\n    \n    @abstractmethod\n    async def connect(self) -> None:\n        \"\"\"建立连接\"\"\"\n        ...\n    \n    @abstractmethod\n    async def execute(self, action: str, params: dict) -> dict:\n        \"\"\"执行操作\"\"\"\n        ...\n    \n    @abstractmethod\n    async def health_check(self) -> bool:\n        \"\"\"健康检查\"\"\"\n        ...",
      "section_ref": "13.1.3",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-3",
      "language": "python",
      "description": "",
      "code": "import httpx\nimport asyncio\nfrom typing import Any\n\nclass APIIntegration(IntegrationBase):\n    \"\"\"REST API集成\"\"\"\n    \n    def __init__(self, config: IntegrationConfig, credentials: dict):\n        super().__init__(config)\n        self.credentials = credentials\n        self._client: httpx.AsyncClient | None = None\n        self._rate_limiter = RateLimiter(config.rate_limit)\n    \n    async def connect(self) -> None:\n        \"\"\"初始化HTTP客户端\"\"\"\n        headers = {}\n        \n        # 根据认证类型设置请求头\n        if self.config.auth_type == \"api_key\":\n            key = self.credentials[\"api_key\"]\n            header_name = self.credentials.get(\"header_name\", \"Authorization\")\n            prefix = self.credentials.get(\"prefix\", \"Bearer\")\n            headers[header_name] = f\"{prefix} {key}\"\n        elif self.config.auth_type == \"basic\":\n            import base64\n            creds = base64.b64encode(\n                f\"{self.credentials['username']}:{self.credentials['password']}\"\n                .encode()\n            ).decode()\n            headers[\"Authorization\"] = f\"Basic {creds}\"\n        \n        self._client = httpx.AsyncClient(\n            base_url=self.config.base_url,\n            headers=headers,\n            timeout=httpx.Timeout(self.config.timeout),\n        )\n    \n    async def execute(self, action: str, params: dict) -> dict:\n        \"\"\"执行API调用\"\"\"\n        await self._rate_limiter.wait()\n        \n        method = params.pop(\"method\", \"GET\")\n        path = params.pop(\"path\", action)\n        \n        for attempt in range(self.config.retry_count):\n            try:\n                response = await self._client.request(\n                    method, path, **params\n                )\n                response.raise_for_status()\n                return response.json()\n            \n            except httpx.HTTPStatusError as e:\n                if e.response.status_code == 429:  # Rate limited\n                    retry_after = int(e.response.headers.get(\"Retry-After\", \"60\"))\n                    await asyncio.sleep(retry_after)\n                    continue\n                elif e.response.status_code >= 500:  # Server error\n                    await asyncio.sleep(2 ** attempt)  # Exponential backoff\n                    continue\n                else:\n                    raise\n    \n    async def health_check(self) -> bool:\n        try:\n            response = await self._client.get(\"/health\")\n            return response.status_code == 200\n        except Exception:\n            return False",
      "section_ref": "13.2.1",
      "runnable": true,
      "dependencies": [
        "httpx"
      ]
    },
    {
      "id": "code-4",
      "language": "python",
      "description": "",
      "code": "from datetime import datetime\nimport httpx\n\nclass OAuth2Manager:\n    \"\"\"OAuth2 Token管理\"\"\"\n    \n    def __init__(self, token_url: str, client_id: str, \n                 client_secret: str, scopes: list[str]):\n        self.token_url = token_url\n        self.client_id = client_id\n        self.client_secret = client_secret\n        self.scopes = scopes\n        self._token: str | None = None\n        self._expires_at: datetime | None = None\n    \n    async def get_token(self) -> str:\n        \"\"\"获取有效Token（自动刷新）\"\"\"\n        if self._token and self._expires_at:\n            if datetime.now() < self._expires_at:\n                return self._token\n        \n        # 请求新Token\n        async with httpx.AsyncClient() as client:\n            response = await client.post(\n                self.token_url,\n                data={\n                    \"grant_type\": \"client_credentials\",\n                    \"client_id\": self.client_id,\n                    \"client_secret\": self.client_secret,\n                    \"scope\": \" \".join(self.scopes),\n                }\n            )\n            token_data = response.json()\n            self._token = token_data[\"access_token\"]\n            expires_in = token_data.get(\"expires_in\", 3600)\n            self._expires_at = datetime.now().timestamp() + expires_in - 300\n        \n        return self._token\n\n# 使用示例：集成GitHub API\ngithub_oauth = OAuth2Manager(\n    token_url=\"https://github.com/login/oauth/access_token\",\n    client_id=\"your_client_id\",\n    client_secret=\"your_client_secret\",\n    scopes=[\"repo\", \"read:org\"]\n)\n\ngithub_integration = APIIntegration(\n    config=IntegrationConfig(\n        name=\"github\",\n        type=IntegrationType.REST_API,\n        base_url=\"https://api.github.com\",\n        auth_type=\"oauth2\"\n    ),\n    credentials={\"oauth_manager\": github_oauth}\n)",
      "section_ref": "13.2.2",
      "runnable": true,
      "dependencies": [
        "httpx"
      ]
    },
    {
      "id": "code-5",
      "language": "python",
      "description": "",
      "code": "async def fetch_all_pages(\n    client: httpx.AsyncClient,\n    first_url: str,\n    page_param: str = \"page\",\n    per_page: int = 100\n) -> list[dict]:\n    \"\"\"自动处理分页，获取所有结果\"\"\"\n    all_results = []\n    page = 1\n    \n    while True:\n        response = await client.get(\n            first_url, \n            params={page_param: page, \"per_page\": per_page}\n        )\n        data = response.json()\n        \n        if not data:\n            break\n        \n        all_results.extend(data)\n        \n        # 检查是否还有更多页\n        link_header = response.headers.get(\"Link\", \"\")\n        if 'rel=\"next\"' not in link_header:\n            break\n        \n        page += 1\n    \n    return all_results",
      "section_ref": "13.2.3",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-6",
      "language": "python",
      "description": "",
      "code": "import asyncpg\nfrom pydantic import BaseModel\n\nclass SQLQueryAgent:\n    \"\"\"数据库查询Agent\"\"\"\n    \n    def __init__(self, connection_string: str, \n                 read_only: bool = True):\n        self.connection_string = connection_string\n        self.read_only = read_only\n        self.pool: asyncpg.Pool | None = None\n    \n    async def connect(self):\n        self.pool = await asyncpg.create_pool(self.connection_string)\n    \n    async def get_schema(self, table_name: str | None = None) -> str:\n        \"\"\"获取数据库Schema信息（供LLM理解表结构）\"\"\"\n        async with self.pool.acquire() as conn:\n            if table_name:\n                columns = await conn.fetch(\"\"\"\n                    SELECT column_name, data_type, is_nullable\n                    FROM information_schema.columns\n                    WHERE table_name = $1\n                    ORDER BY ordinal_position\n                \"\"\", table_name)\n                return self._format_schema(table_name, columns)\n            else:\n                tables = await conn.fetch(\"\"\"\n                    SELECT table_name FROM information_schema.tables\n                    WHERE table_schema = 'public'\n                \"\"\")\n                schema_parts = []\n                for t in tables:\n                    cols = await conn.fetch(\"\"\"\n                        SELECT column_name, data_type, is_nullable\n                        FROM information_schema.columns\n                        WHERE table_name = $1\n                        ORDER BY ordinal_position\n                    \"\"\", t[\"table_name\"])\n                    schema_parts.append(\n                        self._format_schema(t[\"table_name\"], cols)\n                    )\n                return \"\\n\\n\".join(schema_parts)\n    \n    async def execute_query(self, sql: str) -> list[dict]:\n        \"\"\"执行SQL查询（只读模式会验证SQL）\"\"\"\n        if self.read_only:\n            normalized = sql.strip().upper()\n            if any(normalized.startswith(kw) \n                   for kw in [\"INSERT\", \"UPDATE\", \"DELETE\", \"DROP\", \"ALTER\", \"CREATE\"]):\n                raise ValueError(\"只读模式不允许执行写操作\")\n        \n        async with self.pool.acquire() as conn:\n            rows = await conn.fetch(sql)\n            return [dict(row) for row in rows]\n    \n    def _format_schema(self, table: str, columns) -> str:\n        lines = [f\"表: {table}\"]\n        for col in columns:\n            nullable = \"NULL\" if col[\"is_nullable\"] == \"YES\" else \"NOT NULL\"\n            lines.append(f\"  - {col['column_name']}: {col['data_type']} {nullable}\")\n        return \"\\n\".join(lines)\n\n# Agent工具集成\nclass DatabaseTool:\n    def __init__(self, db_agent: SQLQueryAgent):\n        self.db = db_agent\n    \n    async def query(self, question: str, llm) -> str:\n        \"\"\"让LLM根据Schema生成SQL并执行\"\"\"\n        schema = await self.db.get_schema()\n        \n        prompt = f\"\"\"\n        数据库Schema:\n        {schema}\n        \n        用户问题: {question}\n        \n        请生成SQL查询语句。只返回SQL，不要解释。\n        如果问题无法通过SQL回答，返回 \"UNANSWERABLE\"。\n        \"\"\"\n        \n        sql = await llm.generate(prompt)\n        if sql.strip() == \"UNANSWERABLE\":\n            return \"抱歉，该问题无法通过数据库查询回答。\"\n        \n        # 安全检查：防止SQL注入（使用参数化查询更安全）\n        try:\n            results = await self.db.execute_query(sql)\n            if not results:\n                return \"查询结果为空。\"\n            return f\"查询到 {len(results)} 条记录：\\n\" + \\\n                   \"\\n\".join(str(r) for r in results[:20])\n        except Exception as e:\n            return f\"查询执行失败: {e}\"",
      "section_ref": "13.3.1",
      "runnable": true,
      "dependencies": [
        "asyncpg",
        "pydantic"
      ]
    },
    {
      "id": "code-7",
      "language": "python",
      "description": "",
      "code": "from motor.motor_asyncio import AsyncIOMotorClient\n\nclass MongoDBIntegration:\n    \"\"\"MongoDB集成\"\"\"\n    \n    def __init__(self, uri: str, database: str):\n        self.client = AsyncIOMotorClient(uri)\n        self.db = self.client[database]\n    \n    async def find(self, collection: str, query: dict,\n                   projection: dict | None = None,\n                   limit: int = 10) -> list[dict]:\n        \"\"\"查询文档\"\"\"\n        cursor = self.db[collection].find(query, projection).limit(limit)\n        return await cursor.to_list(length=limit)\n    \n    async def aggregate(self, collection: str, \n                        pipeline: list[dict]) -> list[dict]:\n        \"\"\"聚合查询\"\"\"\n        cursor = self.db[collection].aggregate(pipeline)\n        return await cursor.to_list(length=100)\n    \n    async def insert(self, collection: str, document: dict) -> str:\n        \"\"\"插入文档\"\"\"\n        result = await self.db[collection].insert_one(document)\n        return str(result.inserted_id)",
      "section_ref": "13.3.2",
      "runnable": true,
      "dependencies": [
        "motor"
      ]
    },
    {
      "id": "code-8",
      "language": "python",
      "description": "",
      "code": "from aiokafka import AIOKafkaProducer, AIOKafkaConsumer\nimport json\n\nclass KafkaIntegration(IntegrationBase):\n    \"\"\"Kafka消息队列集成\"\"\"\n    \n    def __init__(self, config: IntegrationConfig, \n                 bootstrap_servers: str):\n        super().__init__(config)\n        self.bootstrap_servers = bootstrap_servers\n        self._producer: AIOKafkaProducer | None = None\n    \n    async def connect(self):\n        self._producer = AIOKafkaProducer(\n            bootstrap_servers=self.bootstrap_servers,\n            value_serializer=lambda v: json.dumps(v).encode()\n        )\n        await self._producer.start()\n    \n    async def execute(self, action: str, params: dict) -> dict:\n        if action == \"publish\":\n            await self._producer.send_and_wait(\n                topic=params[\"topic\"],\n                value=params[\"message\"]\n            )\n            return {\"status\": \"published\"}\n        elif action == \"consume\":\n            results = await self._consume_messages(\n                params[\"topic\"], \n                params.get(\"group_id\", \"agent-group\"),\n                max_messages=params.get(\"max_messages\", 10)\n            )\n            return {\"messages\": results}\n    \n    async def _consume_messages(self, topic: str, group_id: str,\n                                 max_messages: int) -> list:\n        consumer = AIOKafkaConsumer(\n            topic,\n            bootstrap_servers=self.bootstrap_servers,\n            group_id=group_id,\n            value_deserializer=lambda m: json.loads(m.decode())\n        )\n        await consumer.start()\n        \n        messages = []\n        async for msg in consumer:\n            messages.append(msg.value)\n            if len(messages) >= max_messages:\n                break\n        \n        await consumer.stop()\n        return messages\n    \n    async def health_check(self) -> bool:\n        try:\n            await self._producer.send_and_wait(\n                \"__health_check_topic\", b\"ping\"\n            )\n            return True\n        except Exception:\n            return False",
      "section_ref": "13.4.1",
      "runnable": true,
      "dependencies": [
        "aiokafka"
      ]
    },
    {
      "id": "code-9",
      "language": "python",
      "description": "",
      "code": "import asyncio\nfrom typing import Callable\n\nclass EventBus:\n    \"\"\"Agent事件总线\"\"\"\n    \n    def __init__(self):\n        self._subscribers: dict[str, list[Callable]] = {}\n        self._queue: asyncio.Queue = asyncio.Queue()\n        self._running = False\n    \n    def subscribe(self, event_type: str, handler: Callable):\n        if event_type not in self._subscribers:\n            self._subscribers[event_type] = []\n        self._subscribers[event_type].append(handler)\n    \n    async def publish(self, event_type: str, data: dict):\n        await self._queue.put({\"type\": event_type, \"data\": data})\n    \n    async def start(self):\n        self._running = True\n        while self._running:\n            event = await self._queue.get()\n            handlers = self._subscribers.get(event[\"type\"], [])\n            for handler in handlers:\n                try:\n                    await handler(event[\"data\"])\n                except Exception as e:\n                    print(f\"事件处理失败: {event['type']} - {e}\")\n\n# 使用：Agent产生的事件可以被其他系统消费\nevent_bus = EventBus()\n\n@event_bus.subscribe(\"agent.task_completed\")\nasync def notify_external_system(data: dict):\n    \"\"\"当Agent完成任务时，通知外部系统\"\"\"\n    await webhook_sender.send(\n        url=data[\"webhook_url\"],\n        payload={\n            \"task_id\": data[\"task_id\"],\n            \"result\": data[\"result\"],\n            \"timestamp\": datetime.now().isoformat()\n        }\n    )",
      "section_ref": "13.4.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-10",
      "language": "python",
      "description": "MCP（Model Context Protocol）是 Anthropic 提出的开放协议，标准化了 LLM/AI 模型与外部工具、数据源的集成方式。它定义了统一的接口规范，让 Agent 可以以标",
      "code": "# MCP工具的标准接口\nclass MCPTool:\n    \"\"\"MCP协议工具实现\"\"\"\n    \n    def __init__(self, server_url: str):\n        self.server_url = server_url\n        self._client = httpx.AsyncClient(base_url=server_url)\n    \n    async def list_tools(self) -> list[dict]:\n        \"\"\"列出所有可用工具\"\"\"\n        response = await self._client.get(\"/tools\")\n        return response.json()[\"tools\"]\n    \n    async def call_tool(self, tool_name: str, \n                        arguments: dict) -> dict:\n        \"\"\"调用工具\"\"\"\n        response = await self._client.post(\"/tools/call\", json={\n            \"name\": tool_name,\n            \"arguments\": arguments\n        })\n        return response.json()\n    \n    async def list_resources(self) -> list[dict]:\n        \"\"\"列出可用资源\"\"\"\n        response = await self._client.get(\"/resources\")\n        return response.json()[\"resources\"]\n    \n    async def read_resource(self, uri: str) -> dict:\n        \"\"\"读取资源内容\"\"\"\n        response = await self._client.get(\"/resources/read\", params={\n            \"uri\": uri\n        })\n        return response.json()",
      "section_ref": "13.5.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-11",
      "language": "python",
      "description": "",
      "code": "class MCPAgentIntegration:\n    \"\"\"将MCP服务器集成到Agent的工具系统中\"\"\"\n    \n    def __init__(self, mcp_server_urls: list[str]):\n        self.mcp_tools: dict[str, MCPTool] = {}\n        for url in mcp_server_urls:\n            tool = MCPTool(url)\n            self.mcp_tools[url] = tool\n    \n    async def discover_all_tools(self) -> list[dict]:\n        \"\"\"发现所有MCP服务器的工具\"\"\"\n        all_tools = []\n        for url, mcp in self.mcp_tools.items():\n            tools = await mcp.list_tools()\n            for tool in tools:\n                tool[\"_mcp_server\"] = url  # 标记来源\n            all_tools.extend(tools)\n        return all_tools\n    \n    async def execute_tool(self, mcp_server: str, \n                           tool_name: str,\n                           args: dict) -> dict:\n        \"\"\"通过MCP协议执行工具\"\"\"\n        return await self.mcp_tools[mcp_server].call_tool(\n            tool_name, args\n        )\n\n# 使用示例\nmcp_integration = MCPAgentIntegration([\n    \"http://localhost:3001\",  # 文件系统MCP服务器\n    \"http://localhost:3002\",  # 数据库MCP服务器\n    \"http://localhost:3003\",  # Web搜索MCP服务器\n])\n\n# Agent发现工具\navailable_tools = await mcp_integration.discover_all_tools()\nprint(f\"发现 {len(available_tools)} 个工具\")\n\n# Agent调用工具\nresult = await mcp_integration.execute_tool(\n    mcp_server=\"http://localhost:3003\",\n    tool_name=\"web_search\",\n    args={\"query\": \"Python Agent frameworks 2024\"}\n)",
      "section_ref": "13.5.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-12",
      "language": "python",
      "description": "",
      "code": "from slack_bolt.async_app import AsyncApp\nfrom slack_sdk.web.async_client import AsyncWebClient\n\nclass SlackIntegration:\n    \"\"\"Slack集成\"\"\"\n    \n    def __init__(self, bot_token: str, signing_secret: str):\n        self.app = AsyncApp(signing_secret=signing_secret)\n        self.client = AsyncWebClient(token=bot_token)\n    \n    async def send_message(self, channel: str, text: str,\n                          blocks: list[dict] | None = None):\n        \"\"\"发送消息到Slack频道\"\"\"\n        kwargs = {\"channel\": channel, \"text\": text}\n        if blocks:\n            kwargs[\"blocks\"] = blocks\n        return await self.client.chat_postMessage(**kwargs)\n    \n    async def update_message(self, channel: str, ts: str,\n                             text: str, blocks: list[dict] | None = None):\n        \"\"\"更新已有消息\"\"\"\n        kwargs = {\"channel\": channel, \"ts\": ts, \"text\": text}\n        if blocks:\n            kwargs[\"blocks\"] = blocks\n        return await self.client.chat_update(**kwargs)\n    \n    def on_message(self, pattern: str | None = None):\n        \"\"\"注册消息处理器\"\"\"\n        def decorator(handler):\n            @self.app.event(\"message\")\n            async def handle(event, say):\n                if pattern and pattern not in event.get(\"text\", \"\"):\n                    return\n                await handler(event, say)\n            return handle\n        return decorator\n\n# Agent与Slack集成示例\nslack = SlackIntegration(bot_token=\"xoxb-...\", signing_secret=\"...\")\n\n@slack.on_message(\"@agent\")\nasync def handle_agent_mention(event, say):\n    \"\"\"当用户@Agent时触发\"\"\"\n    user_message = event[\"text\"].replace(\"@agent\", \"\").strip()\n    \n    # 调用Agent处理\n    agent = get_agent()\n    response = await agent.run(user_message)\n    \n    # 发送结果\n    await say(response)",
      "section_ref": "13.6.1",
      "runnable": true,
      "dependencies": [
        "slack_bolt",
        "slack_sdk"
      ]
    },
    {
      "id": "code-13",
      "language": "python",
      "description": "",
      "code": "class GitHubIntegration:\n    \"\"\"GitHub API集成\"\"\"\n    \n    def __init__(self, token: str):\n        self.headers = {\n            \"Authorization\": f\"token {token}\",\n            \"Accept\": \"application/vnd.github.v3+json\"\n        }\n        self.base_url = \"https://api.github.com\"\n    \n    async def create_issue(self, owner: str, repo: str,\n                          title: str, body: str,\n                          labels: list[str] | None = None) -> dict:\n        \"\"\"创建Issue\"\"\"\n        data = {\"title\": title, \"body\": body}\n        if labels:\n            data[\"labels\"] = labels\n        \n        async with httpx.AsyncClient() as client:\n            response = await client.post(\n                f\"{self.base_url}/repos/{owner}/{repo}/issues\",\n                headers=self.headers, json=data\n            )\n            return response.json()\n    \n    async def create_pr(self, owner: str, repo: str,\n                       title: str, head: str, base: str,\n                       body: str) -> dict:\n        \"\"\"创建Pull Request\"\"\"\n        data = {\n            \"title\": title, \"head\": head, \n            \"base\": base, \"body\": body\n        }\n        async with httpx.AsyncClient() as client:\n            response = await client.post(\n                f\"{self.base_url}/repos/{owner}/{repo}/pulls\",\n                headers=self.headers, json=data\n            )\n            return response.json()\n    \n    async def review_pr(self, owner: str, repo: str, \n                       pr_number: int, body: str,\n                       event: str = \"COMMENT\") -> dict:\n        \"\"\"PR Review评论\"\"\"\n        async with httpx.AsyncClient() as client:\n            response = await client.post(\n                f\"{self.base_url}/repos/{owner}/{repo}/pulls/\"\n                f\"{pr_number}/reviews\",\n                headers=self.headers,\n                json={\"body\": body, \"event\": event}\n            )\n            return response.json()",
      "section_ref": "13.6.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-14",
      "language": "python",
      "description": "",
      "code": "class UnifiedToolAdapter:\n    \"\"\"统一工具适配器：将不同来源的工具统一为Agent可用的格式\"\"\"\n    \n    def __init__(self):\n        self._adapters: dict[str, Callable] = {}\n    \n    def register(self, integration_type: str, \n                 adapter: Callable):\n        self._adapters[integration_type] = adapter\n    \n    async def adapt(self, integration: Any) -> dict:\n        \"\"\"将任意集成转换为统一的Tool Schema\"\"\"\n        adapter = self._adapters.get(integration.config.type)\n        if not adapter:\n            raise ValueError(\n                f\"不支持的集成类型: {integration.config.type}\"\n            )\n        return await adapter(integration)\n\n# 适配REST API为Tool\nasync def adapt_rest_api(integration: APIIntegration) -> dict:\n    return {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": integration.config.name,\n            \"description\": f\"调用 {integration.config.name} API\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"action\": {\"type\": \"string\", \n                              \"description\": \"API操作路径\"},\n                    \"params\": {\"type\": \"object\", \n                              \"description\": \"请求参数\"}\n                }\n            }\n        },\n        \"handler\": integration.execute\n    }\n\n# 适配数据库为Tool\nasync def adapt_database(db: SQLQueryAgent) -> dict:\n    return {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"database_query\",\n            \"description\": \"查询数据库获取数据\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"question\": {\"type\": \"string\", \n                               \"description\": \"自然语言问题\"}\n                }\n            }\n        },\n        \"handler\": lambda params: db.query(params[\"question\"])\n    }",
      "section_ref": "13.7.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-15",
      "language": "python",
      "description": "",
      "code": "class CircuitBreaker:\n    \"\"\"熔断器：防止故障传播\"\"\"\n    \n    CLOSED = \"closed\"    # 正常状态\n    OPEN = \"open\"        # 熔断状态\n    HALF_OPEN = \"half_open\"  # 半开状态（试探）\n    \n    def __init__(self, failure_threshold: int = 5,\n                 recovery_timeout: float = 60.0):\n        self.failure_threshold = failure_threshold\n        self.recovery_timeout = recovery_timeout\n        self.state = self.CLOSED\n        self._failure_count = 0\n        self._last_failure_time = 0\n    \n    def can_execute(self) -> bool:\n        if self.state == self.CLOSED:\n            return True\n        elif self.state == self.OPEN:\n            if time.time() - self._last_failure_time > self.recovery_timeout:\n                self.state = self.HALF_OPEN\n                return True\n            return False\n        else:  # HALF_OPEN\n            return True\n    \n    def record_success(self):\n        self._failure_count = 0\n        self.state = self.CLOSED\n    \n    def record_failure(self):\n        self._failure_count += 1\n        self._last_failure_time = time.time()\n        if self._failure_count >= self.failure_threshold:\n            self.state = self.OPEN",
      "section_ref": "13.7.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-16",
      "language": "python",
      "description": "",
      "code": "import time\n\nclass RateLimiter:\n    \"\"\"令牌桶限流器\"\"\"\n    \n    def __init__(self, max_requests: int, \n                 per_seconds: float = 60.0):\n        self.max_requests = max_requests\n        self.per_seconds = per_seconds\n        self._tokens = max_requests\n        self._last_refill = time.time()\n        self._lock = asyncio.Lock()\n    \n    async def wait(self):\n        async with self._lock:\n            now = time.time()\n            elapsed = now - self._last_refill\n            \n            # 补充令牌\n            refill = (elapsed / self.per_seconds) * self.max_requests\n            self._tokens = min(self.max_requests, \n                             self._tokens + refill)\n            self._last_refill = now\n            \n            if self._tokens < 1:\n                wait_time = (1 - self._tokens) / self.max_requests \\\n                          * self.per_seconds\n                await asyncio.sleep(wait_time)\n                self._tokens = 0\n            else:\n                self._tokens -= 1",
      "section_ref": "13.7.3",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-17",
      "language": "python",
      "description": "",
      "code": "import os\nfrom pathlib import Path\n\nclass SecretManager:\n    \"\"\"凭证管理\"\"\"\n    \n    def __init__(self):\n        self._secrets: dict[str, str] = {}\n        self._load_from_env()\n    \n    def _load_from_env(self):\n        \"\"\"从环境变量加载敏感信息\"\"\"\n        prefix = \"AGENT_SECRET_\"\n        for key, value in os.environ.items():\n            if key.startswith(prefix):\n                name = key[len(prefix):].lower()\n                self._secrets[name] = value\n    \n    def get(self, name: str) -> str:\n        if name not in self._secrets:\n            raise ValueError(f\"未找到凭证: {name}\")\n        return self._secrets[name]\n    \n    def get masked(self, name: str) -> str:\n        \"\"\"获取脱敏后的凭证\"\"\"\n        value = self.get(name)\n        return value[:4] + \"****\" + value[-4:]",
      "section_ref": "13.8.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-18",
      "language": "python",
      "description": "",
      "code": "import re\n\nclass InputValidator:\n    \"\"\"输入验证器\"\"\"\n    \n    # SQL注入检测模式\n    SQL_PATTERNS = [\n        r\"(?i)(DROP|DELETE|TRUNCATE|ALTER|CREATE)\\s+TABLE\",\n        r\"(?i)(UNION\\s+SELECT|OR\\s+1\\s*=\\s*1)\",\n        r\";\\s*(DROP|DELETE|INSERT|UPDATE)\",\n    ]\n    \n    # 路径遍历检测\n    PATH_TRAVERSAL = r\"\\.\\./|\\.\\.\\\\\"\n    \n    @classmethod\n    def validate_sql_input(cls, input_str: str) -> bool:\n        for pattern in cls.SQL_PATTERNS:\n            if re.search(pattern, input_str):\n                return False\n        return True\n    \n    @classmethod\n    def validate_path(cls, path: str, \n                     allowed_dirs: list[str]) -> bool:\n        if re.search(cls.PATH_TRAVERSAL, path):\n            return False\n        resolved = Path(path).resolve()\n        return any(\n            str(resolved).startswith(d) \n            for d in allowed_dirs\n        )\n\nclass DataSanitizer:\n    \"\"\"数据脱敏\"\"\"\n    \n    PII_PATTERNS = {\n        \"email\": r\"\\b[\\w.-]+@[\\w.-]+\\.\\w+\\b\",\n        \"phone\": r\"\\b1[3-9]\\d{9}\\b\",\n        \"id_card\": r\"\\b\\d{17}[\\dXx]\\b\",\n        \"credit_card\": r\"\\b\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b\",\n    }\n    \n    @classmethod\n    def sanitize(cls, text: str) -> str:\n        \"\"\"脱敏PII数据\"\"\"\n        result = text\n        for pii_type, pattern in cls.PII_PATTERNS.items():\n            result = re.sub(\n                pattern, \n                f\"[{pii_type}_REDACTED]\", \n                result\n            )\n        return result",
      "section_ref": "13.8.2",
      "runnable": true,
      "dependencies": []
    }
  ],
  "tables": [
    {
      "headers": [
        "挑战",
        "描述",
        "典型解决方案"
      ],
      "data": [
        [
          "API多样性",
          "不同的认证方式、数据格式、错误码",
          "统一适配层"
        ],
        [
          "认证安全",
          "凭证管理、Token刷新、权限控制",
          "Secret Manager + OAuth"
        ],
        [
          "数据格式",
          "JSON/XML/Protobuf，Schema差异",
          "数据转换层"
        ],
        [
          "错误处理",
          "超时、限流、服务不可用",
          "重试 + 熔断 + 降级"
        ],
        [
          "幂等性",
          "重复调用导致副作用",
          "幂等设计 + 请求去重"
        ],
        [
          "数据一致性",
          "分布式事务",
          "最终一致性 + 补偿"
        ]
      ]
    }
  ],
  "key_takeaways": [],
  "common_pitfalls": [],
  "related_chapters": [
    "ch06"
  ]
}