{
  "metadata": {
    "id": "ch06",
    "title": "第6章：工具调用与 Function Calling",
    "volume": "vol2",
    "volume_title": "基础篇",
    "word_count": 1988,
    "difficulty": "beginner",
    "prerequisites": [
      "ch04",
      "ch05"
    ],
    "key_concepts": [
      "Function Calling 机制",
      "什么是 Function Calling",
      "Function Calling vs 传统方式",
      "主流模型的 Function Calling 支持",
      "工具定义与描述",
      "JSON Schema 规范",
      "工具描述的艺术",
      "从 Python 函数自动生成工具定义",
      "工具注册与发现",
      "工具注册中心",
      "工具发现优化",
      "并行工具调用",
      "并行调用的意义",
      "并行调用实现",
      "OpenAI 的并行调用"
    ],
    "learning_objectives": [],
    "estimated_tokens": 1193,
    "source_file": "vol2/ch06_工具调用与Function_Calling.md"
  },
  "overview": "",
  "sections": [
    {
      "id": "6.1",
      "title": "6.1 Function Calling 机制",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "6.1.1",
          "title": "6.1.1 什么是 Function Calling",
          "content": "Function Calling（函数调用）是 LLM 提供的一种结构化能力，允许模型生成符合预定义 Schema 的函数调用请求，而非纯文本输出。\n\n关键理解：**LLM 本身不执行函数**。它只是告诉你\"我想调用这个函数，参数是这样的\"。实际执行由你的应用代码完成，然后将结果返回给 LLM。"
        },
        {
          "id": "6.1.2",
          "title": "6.1.2 Function Calling vs 传统方式",
          "content": "在 Function Calling 出现之前，让 LLM 调用工具通常使用基于文本解析的方式："
        },
        {
          "id": "6.1.3",
          "title": "6.1.3 主流模型的 Function Calling 支持",
          "content": "| 提供商 | API | 特点 |\n|--------|-----|------|\n| OpenAI | `tools` 参数 | 最成熟，支持并行调用 |\n| Anthropic | `tool_choice` + `tools` | 支持 cache_control 优化 |\n| Google Gemini | `function_declarations` | 支持原生 Python 函数导入 |\n| Anthropic Bedrock | 同 Anthropic | AWS 集成 |\n\n\n---"
        }
      ]
    },
    {
      "id": "6.2",
      "title": "6.2 工具定义与描述",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "6.2.1",
          "title": "6.2.1 JSON Schema 规范",
          "content": "工具的参数定义遵循 JSON Schema 规范。LLM 依赖这些定义来理解工具能做什么、需要什么参数。"
        },
        {
          "id": "6.2.2",
          "title": "6.2.2 工具描述的艺术",
          "content": "工具描述是 LLM 决定是否调用该工具、如何调用该工具的唯一依据。写好描述至关重要。\n\n\n**工具描述的黄金法则：**\n\n1. **说清楚做什么**，不要只说名字\n2. **说清楚适用场景**，帮助 LLM 做出正确选择\n3. **说清楚不适合的场景**，避免误用\n4. **参数描述要具体**，包含格式、范围、示例\n5. **标注必填/可选**，减少无效调用"
        },
        {
          "id": "6.2.3",
          "title": "6.2.3 从 Python 函数自动生成工具定义",
          "content": "手动编写 JSON Schema 既繁琐又容易出错。我们可以从 Python 函数自动生成：\n\n\n---"
        }
      ]
    },
    {
      "id": "6.3",
      "title": "6.3 工具注册与发现",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "6.3.1",
          "title": "6.3.1 工具注册中心",
          "content": "当 Agent 拥有大量工具时，需要一个统一的注册和发现机制："
        },
        {
          "id": "6.3.2",
          "title": "6.3.2 工具发现优化",
          "content": "当工具数量很多时（如超过20个），直接将所有工具定义发给 LLM 会导致：\n\n1. Token 消耗大\n2. LLM 选择准确率下降\n3. 响应变慢\n\n解决方案——两级发现策略：\n\n\n---"
        }
      ]
    },
    {
      "id": "6.4",
      "title": "6.4 并行工具调用",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "6.4.1",
          "title": "6.4.1 并行调用的意义",
          "content": "当 LLM 判断多个工具调用之间没有依赖关系时，可以并行发起多个调用，显著提升效率。"
        },
        {
          "id": "6.4.2",
          "title": "6.4.2 并行调用实现",
          "content": ""
        },
        {
          "id": "6.4.3",
          "title": "6.4.3 OpenAI 的并行调用",
          "content": "OpenAI API 原生支持并行工具调用：\n\n\n---"
        }
      ]
    },
    {
      "id": "6.5",
      "title": "6.5 工具调用的错误处理",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "6.5.1",
          "title": "6.5.1 常见错误类型",
          "content": ""
        },
        {
          "id": "6.5.2",
          "title": "6.5.2 错误处理中间件",
          "content": ""
        },
        {
          "id": "6.5.3",
          "title": "6.5.3 将错误信息返回给 LLM",
          "content": "---"
        }
      ]
    },
    {
      "id": "6.6",
      "title": "6.6 自定义工具开发",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "6.6.1",
          "title": "6.6.1 工具开发框架",
          "content": ""
        },
        {
          "id": "6.6.2",
          "title": "6.6.2 实战：开发一个代码执行工具",
          "content": ""
        },
        {
          "id": "6.6.3",
          "title": "6.6.3 实战：开发一个 API 调用工具",
          "content": ""
        },
        {
          "id": "6.6.4",
          "title": "6.6.4 工具组合与编排",
          "content": "有时一个高级功能需要组合多个基础工具：\n\n\n---"
        }
      ]
    },
    {
      "id": "6.7",
      "title": "6.7 常见陷阱与最佳实践",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "6.7.1",
          "title": "6.7.1 常见陷阱",
          "content": "#### 陷阱1：工具定义不一致\n\n\n#### 陷阱2：不限制工具的副作用\n\n\n#### 陷阱3：工具返回信息过多"
        },
        {
          "id": "6.7.2",
          "title": "6.7.2 最佳实践",
          "content": "---"
        }
      ]
    },
    {
      "id": "6.8",
      "title": "6.8 本章小结",
      "level": 2,
      "content": "本章我们全面探讨了工具调用与 Function Calling 的实现：\n\n1. **Function Calling 机制**：LLM 生成结构化调用请求，应用代码执行并返回结果\n2. **工具定义**：JSON Schema 规范，工具描述的艺术，从 Python 函数自动生成定义\n3. **工具注册与发现**：注册中心模式，智能工具选择器\n4. **并行工具调用**：提升多工具场景下的执行效率\n5. **错误处理**：分类错误体系、重试策略、错误信息格式化\n6. **自定义工具开发**：基础框架、代码执行工具、API 调用工具、组合工具\n\n**核心洞察：** Function Calling 是 Agent 的\"手和脚\"。精心设计的工具定义是 Agent 高效工作的前提。好的工具系统应该安全、可靠、可观测，并且对 LLM 友好。\n\n---\n\n> **下一章**：[第7章：记忆与上下文管理](ch07_记忆与上下文管理.md) —— 让 Agent 拥有\"记忆\"，不再每次都从零开始。",
      "subsections": []
    }
  ],
  "code_blocks": [
    {
      "id": "code-1",
      "language": "text",
      "description": "关键理解：LLM 本身不执行函数。它只是告诉你\"我想调用这个函数，参数是这样的\"。实际执行由你的应用代码完成，然后将结果返回给 LLM。",
      "code": "用户请求: \"北京今天天气怎么样？\"\n        │\n        ▼\n┌──────────────────────────────────────────┐\n│  LLM 推理:                                │\n│  \"用户想知道北京天气，我应该调用天气查询工具\" │\n│                                          │\n│  输出 (不是文本！):                        │\n│  {                                       │\n│    \"function\": \"get_weather\",            │\n│    \"arguments\": {\"city\": \"北京\"}         │\n│  }                                       │\n└──────────────┬───────────────────────────┘\n               │\n               ▼\n┌──────────────────────────────────────────┐\n│  你的应用代码:                             │\n│  1. 解析 LLM 的函数调用请求                │\n│  2. 调用实际的天气 API                     │\n│  3. 将结果返回给 LLM                      │\n└──────────────┬───────────────────────────┘\n               │\n               ▼\n┌──────────────────────────────────────────┐\n│  LLM 基于工具结果生成自然语言回答:          │\n│  \"北京今天晴，温度 25°C，风力 3 级。\"       │\n└──────────────────────────────────────────┘",
      "section_ref": "6.1.1",
      "runnable": false,
      "dependencies": []
    },
    {
      "id": "code-2",
      "language": "python",
      "description": "在 Function Calling 出现之前，让 LLM 调用工具通常使用基于文本解析的方式：",
      "code": "# 传统方式：基于文本解析（脆弱、不稳定）\ntraditional_prompt = \"\"\"你可以使用以下命令：\n- WEATHER <城市>: 查询天气\n- CALC <表达式>: 计算数学表达式\n\n用户问：北京天气怎么样？\"\"\"\n\n# LLM 可能输出: \"WEATHER 北京\" 或 \"WEATHER(city=北京)\" 或其他变体\n# 你需要用正则表达式/字符串匹配来解析——非常脆弱\n\n# Function Calling 方式（结构化、可靠）\n# LLM 输出的是标准化的 JSON:\n# {\"name\": \"get_weather\", \"arguments\": {\"city\": \"北京\"}}\n# 解析可靠，不依赖文本格式",
      "section_ref": "6.1.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-3",
      "language": "python",
      "description": "| Anthropic Bedrock | 同 Anthropic | AWS 集成 |",
      "code": "# OpenAI Function Calling 基本示例\nfrom openai import OpenAI\n\nclient = OpenAI()\n\n# 1. 定义工具\ntools = [\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"get_weather\",\n            \"description\": \"获取指定城市的当前天气信息\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"city\": {\n                        \"type\": \"string\",\n                        \"description\": \"城市名称，如 北京、上海、New York\"\n                    },\n                    \"unit\": {\n                        \"type\": \"string\",\n                        \"enum\": [\"celsius\", \"fahrenheit\"],\n                        \"description\": \"温度单位\"\n                    }\n                },\n                \"required\": [\"city\"]\n            }\n        }\n    }\n]\n\n# 2. 发送请求\nresponse = client.chat.completions.create(\n    model=\"gpt-4o\",\n    messages=[{\"role\": \"user\", \"content\": \"北京和上海今天哪个热？\"}],\n    tools=tools,\n    tool_choice=\"auto\"  # 让模型自动决定是否调用工具\n)\n\n# 3. 检查是否有工具调用\nmessage = response.choices[0].message\nif message.tool_calls:\n    for tool_call in message.tool_calls:\n        print(f\"工具: {tool_call.function.name}\")\n        print(f\"参数: {tool_call.function.arguments}\")\n        # 输出:\n        # 工具: get_weather\n        # 参数: {\"city\": \"北京\", \"unit\": \"celsius\"}",
      "section_ref": "6.1.3",
      "runnable": true,
      "dependencies": [
        "openai"
      ]
    },
    {
      "id": "code-4",
      "language": "python",
      "description": "工具的参数定义遵循 JSON Schema 规范。LLM 依赖这些定义来理解工具能做什么、需要什么参数。",
      "code": "# 完整的工具定义示例\ntool_definition = {\n    \"type\": \"function\",\n    \"function\": {\n        \"name\": \"search_database\",\n        \"description\": \"\"\"在数据库中搜索记录。支持按字段筛选、排序和分页。\n        \n        适合场景：查找用户信息、订单记录、产品数据等结构化数据。\n        不适合：全文搜索（使用 search_documents）、实时数据（使用 get_realtime_data）。\n        \"\"\",\n        \"parameters\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"users\", \"orders\", \"products\", \"reviews\"],\n                    \"description\": \"要查询的数据表\"\n                },\n                \"query\": {\n                    \"type\": \"object\",\n                    \"description\": \"查询条件\",\n                    \"properties\": {\n                        \"field\": {\n                            \"type\": \"string\",\n                            \"description\": \"查询字段名\"\n                        },\n                        \"operator\": {\n                            \"type\": \"string\",\n                            \"enum\": [\"eq\", \"ne\", \"gt\", \"lt\", \"gte\", \"lte\", \"like\", \"in\"],\n                            \"description\": \"比较运算符\"\n                        },\n                        \"value\": {\n                            \"description\": \"查询值，类型根据字段而定\"\n                        }\n                    },\n                    \"required\": [\"field\", \"operator\", \"value\"]\n                },\n                \"sort_by\": {\n                    \"type\": \"string\",\n                    \"description\": \"排序字段\"\n                },\n                \"sort_order\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"asc\", \"desc\"],\n                    \"default\": \"desc\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\",\n                    \"minimum\": 1,\n                    \"maximum\": 100,\n                    \"default\": 10,\n                    \"description\": \"返回结果数量\"\n                }\n            },\n            \"required\": [\"table\", \"query\"]\n        }\n    }\n}",
      "section_ref": "6.2.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-5",
      "language": "python",
      "description": "工具描述是 LLM 决定是否调用该工具、如何调用该工具的唯一依据。写好描述至关重要。",
      "code": "# ❌ 糟糕的描述\nbad_tools = [\n    {\n        \"name\": \"db\",\n        \"description\": \"数据库操作\",\n        \"parameters\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"q\": {\"type\": \"string\"},\n                \"opts\": {\"type\": \"object\"}\n            }\n        }\n    }\n]\n\n# ✅ 优秀的描述\ngood_tools = [\n    {\n        \"name\": \"query_user_orders\",\n        \"description\": \"\"\"查询指定用户的历史订单。\n        \n        返回订单列表，包含订单号、金额、状态、创建时间。\n        按创建时间倒序排列。\n        \n        注意：只能查询已完成的订单，进行中的订单请使用 get_active_order。\n        \"\"\",\n        \"parameters\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"user_id\": {\n                    \"type\": \"string\",\n                    \"description\": \"用户唯一标识符\"\n                },\n                \"status\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"completed\", \"cancelled\", \"refunded\"],\n                    \"description\": \"订单状态筛选，不传则返回所有状态\"\n                },\n                \"date_from\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"起始日期 (YYYY-MM-DD)\"\n                },\n                \"date_to\": {\n                    \"type\": \"string\",\n                    \"format\": \"date\",\n                    \"description\": \"截止日期 (YYYY-MM-DD)\"\n                }\n            },\n            \"required\": [\"user_id\"]\n        }\n    }\n]",
      "section_ref": "6.2.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-6",
      "language": "python",
      "description": "手动编写 JSON Schema 既繁琐又容易出错。我们可以从 Python 函数自动生成：",
      "code": "import inspect\nfrom typing import get_type_hints, get_origin, get_args\n\ndef function_to_tool(\n    func: callable,\n    name: str | None = None,\n    description: str | None = None\n) -> dict:\n    \"\"\"将 Python 函数转换为工具定义\"\"\"\n    \n    # 获取函数信息\n    sig = inspect.signature(func)\n    hints = get_type_hints(func)\n    doc = inspect.getdoc(func) or \"\"\n    \n    # 解析参数\n    properties = {}\n    required = []\n    \n    for param_name, param in sig.parameters.items():\n        if param_name == \"self\":\n            continue\n        \n        param_type = hints.get(param_name, str)\n        param_info = {\"type\": _python_type_to_json_type(param_type)}\n        \n        # 默认值\n        if param.default != inspect.Parameter.empty:\n            param_info[\"default\"] = param.default\n        else:\n            required.append(param_name)\n        \n        # 从 docstring 提取参数描述\n        param_desc = _extract_param_description(doc, param_name)\n        if param_desc:\n            param_info[\"description\"] = param_desc\n        \n        properties[param_name] = param_info\n    \n    return {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": name or func.__name__,\n            \"description\": description or doc.split(\"\\n\\n\")[0] if doc else \"\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": properties,\n                \"required\": required\n            }\n        }\n    }\n\n\ndef _python_type_to_json_type(python_type) -> str:\n    \"\"\"将 Python 类型映射为 JSON Schema 类型\"\"\"\n    type_map = {\n        str: \"string\",\n        int: \"integer\",\n        float: \"number\",\n        bool: \"boolean\",\n        list: \"array\",\n        dict: \"object\",\n    }\n    \n    if python_type in type_map:\n        return type_map[python_type]\n    \n    origin = get_origin(python_type)\n    if origin is list:\n        args = get_args(python_type)\n        return \"array\"\n    \n    return \"string\"  # 默认\n\n\ndef _extract_param_description(docstring: str, param_name: str) -> str:\n    \"\"\"从 docstring 提取参数描述\"\"\"\n    import re\n    # 匹配 Args: 部分\n    match = re.search(\n        rf'{param_name}\\s*[:：]\\s*(.+?)(?:\\n|$)',\n        docstring,\n        re.IGNORECASE\n    )\n    return match.group(1).strip() if match else \"\"\n\n\n# ===== 使用示例 =====\n\ndef search_documents(\n    query: str,\n    top_k: int = 5,\n    filter_category: str | None = None\n) -> list[dict]:\n    \"\"\"在文档库中搜索相关文档。\n    \n    Args:\n        query: 搜索关键词或问题\n        top_k: 返回的最大文档数量，默认5\n        filter_category: 按类别筛选，如 \"技术文档\"、\"API参考\"\n    \n    Returns:\n        匹配的文档列表，每个文档包含 title, content, score\n    \"\"\"\n    pass\n\n# 自动生成工具定义\ntool_def = function_to_tool(search_documents)\nprint(tool_def)\n# 输出标准的 OpenAI tools 格式",
      "section_ref": "6.2.3",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-7",
      "language": "python",
      "description": "当 Agent 拥有大量工具时，需要一个统一的注册和发现机制：",
      "code": "from typing import Any, Callable\nfrom pydantic import BaseModel, Field, create_model\nfrom enum import Enum\n\nclass ToolCategory(Enum):\n    \"\"\"工具分类\"\"\"\n    SEARCH = \"search\"          # 搜索与检索\n    CALCULATION = \"calc\"       # 计算与数学\n    FILE_IO = \"file\"           # 文件操作\n    COMMUNICATION = \"comm\"     # 通信与消息\n    DATA_ACCESS = \"data\"       # 数据访问\n    SYSTEM = \"system\"          # 系统操作\n    CUSTOM = \"custom\"          # 自定义\n\nclass ToolMeta(BaseModel):\n    \"\"\"工具元数据\"\"\"\n    name: str\n    description: str\n    category: ToolCategory\n    version: str = \"1.0.0\"\n    requires_auth: bool = False\n    rate_limit: int | None = None  # 每分钟调用次数限制\n    timeout_seconds: float = 30.0\n    cost_per_call: float = 0.0     # 每次调用的成本（美元）\n\nclass RegisteredTool:\n    \"\"\"已注册的工具\"\"\"\n    \n    def __init__(\n        self,\n        handler: Callable,\n        definition: dict,\n        meta: ToolMeta\n    ):\n        self.handler = handler\n        self.definition = definition\n        self.meta = meta\n        self._call_count = 0\n        self._error_count = 0\n    \n    def execute(self, **kwargs) -> Any:\n        \"\"\"执行工具\"\"\"\n        import time\n        self._call_count += 1\n        \n        start = time.time()\n        try:\n            # 参数验证（使用 Pydantic）\n            validated = self._validate_args(kwargs)\n            result = self.handler(**validated)\n            return {\"status\": \"success\", \"data\": result}\n        except Exception as e:\n            self._error_count += 1\n            return {\"status\": \"error\", \"error\": str(e)}\n        finally:\n            duration = time.time() - start\n            if duration > self.meta.timeout_seconds:\n                print(f\"⚠️ 工具 {self.meta.name} 执行超时: {duration:.1f}s\")\n    \n    def _validate_args(self, args: dict) -> dict:\n        \"\"\"验证参数\"\"\"\n        # 从工具定义构建 Pydantic 模型并验证\n        schema = self.definition[\"function\"][\"parameters\"]\n        properties = schema.get(\"properties\", {})\n        \n        validated = {}\n        for key, value in args.items():\n            if key in properties:\n                validated[key] = value\n            else:\n                print(f\"⚠️ 未知参数: {key}\")\n        \n        # 检查必填参数\n        for required in schema.get(\"required\", []):\n            if required not in validated:\n                raise ValueError(f\"缺少必填参数: {required}\")\n        \n        return validated\n    \n    @property\n    def stats(self) -> dict:\n        return {\n            \"name\": self.meta.name,\n            \"calls\": self._call_count,\n            \"errors\": self._error_count,\n            \"error_rate\": (\n                self._error_count / self._call_count\n                if self._call_count > 0 else 0\n            )\n        }\n\n\nclass ToolRegistry:\n    \"\"\"工具注册中心\"\"\"\n    \n    def __init__(self):\n        self._tools: dict[str, RegisteredTool] = {}\n    \n    def register(\n        self,\n        handler: Callable,\n        definition: dict,\n        meta: ToolMeta\n    ):\n        \"\"\"注册工具\"\"\"\n        tool = RegisteredTool(handler, definition, meta)\n        self._tools[meta.name] = tool\n    \n    def register_from_function(\n        self,\n        func: Callable,\n        category: ToolCategory = ToolCategory.CUSTOM,\n        description: str | None = None,\n        **meta_kwargs\n    ):\n        \"\"\"从 Python 函数注册\"\"\"\n        tool_def = function_to_tool(func, description=description)\n        meta = ToolMeta(\n            name=func.__name__,\n            description=description or inspect.getdoc(func) or \"\",\n            category=category,\n            **meta_kwargs\n        )\n        self.register(func, tool_def, meta)\n    \n    def get(self, name: str) -> RegisteredTool | None:\n        \"\"\"获取工具\"\"\"\n        return self._tools.get(name)\n    \n    def get_all_definitions(self) -> list[dict]:\n        \"\"\"获取所有工具定义（用于发送给 LLM）\"\"\"\n        return [tool.definition for tool in self._tools.values()]\n    \n    def get_definitions_by_category(self, category: ToolCategory) -> list[dict]:\n        \"\"\"按分类获取工具定义\"\"\"\n        return [\n            tool.definition\n            for tool in self._tools.values()\n            if tool.meta.category == category\n        ]\n    \n    def discover(self, query: str) -> list[str]:\n        \"\"\"根据自然语言描述发现相关工具\"\"\"\n        \"\"\"简单的关键词匹配发现\"\"\"\n        relevant = []\n        query_lower = query.lower()\n        \n        for name, tool in self._tools.items():\n            score = 0\n            desc = tool.meta.description.lower()\n            name_lower = name.lower()\n            \n            # 关键词匹配\n            for word in query_lower.split():\n                if word in desc:\n                    score += 2\n                if word in name_lower:\n                    score += 3\n            \n            if score > 0:\n                relevant.append((score, name))\n        \n        relevant.sort(reverse=True)\n        return [name for _, name in relevant]\n    \n    def list_tools(self) -> list[dict]:\n        \"\"\"列出所有工具及其状态\"\"\"\n        return [tool.stats for tool in self._tools.values()]\n\n\n# ===== 使用示例 =====\nregistry = ToolRegistry()\n\n# 注册工具\nregistry.register_from_function(\n    search_documents,\n    category=ToolCategory.SEARCH,\n    requires_auth=False\n)\n\nregistry.register_from_function(\n    get_weather,\n    category=ToolCategory.DATA_ACCESS,\n    rate_limit=60\n)\n\n# 发现工具\nprint(registry.discover(\"帮我查一下天气\"))  # ['get_weather']\nprint(registry.discover(\"搜索相关文档\"))    # ['search_documents']\n\n# 获取所有工具定义\nall_tools = registry.get_all_definitions()",
      "section_ref": "6.3.1",
      "runnable": true,
      "dependencies": [
        "pydantic"
      ]
    },
    {
      "id": "code-8",
      "language": "python",
      "description": "解决方案——两级发现策略：",
      "code": "class SmartToolSelector:\n    \"\"\"智能工具选择器\"\"\"\n    \n    def __init__(self, registry: ToolRegistry, llm):\n        self.registry = registry\n        self.llm = llm\n    \n    def select_tools(self, query: str, max_tools: int = 5) -> list[dict]:\n        \"\"\"为查询选择最相关的工具\"\"\"\n        \n        # 第一级：快速关键词过滤\n        candidate_names = self.registry.discover(query)\n        \n        if len(candidate_names) <= max_tools:\n            # 候选工具不太多，直接返回\n            return [\n                self.registry.get(name).definition\n                for name in candidate_names\n            ]\n        \n        # 第二级：LLM 精选\n        candidates = [\n            {\n                \"name\": name,\n                \"description\": self.registry.get(name).meta.description\n            }\n            for name in candidate_names\n        ]\n        \n        prompt = f\"\"\"用户请求：{query}\n\n以下是可以使用的工具：\n{json.dumps(candidates, ensure_ascii=False, indent=2)}\n\n请选择最相关的 {max_tools} 个工具来处理这个请求。\n只返回工具名称的 JSON 数组，如 [\"tool1\", \"tool2\"]。\"\"\"\n        \n        response = self.llm.chat(\n            messages=[{\"role\": \"user\", \"content\": prompt}],\n            temperature=0.0\n        )\n        \n        try:\n            selected_names = json.loads(response.content)\n            return [\n                self.registry.get(name).definition\n                for name in selected_names\n                if self.registry.get(name)\n            ]\n        except json.JSONDecodeError:\n            # 降级：返回前 N 个候选\n            return [\n                self.registry.get(name).definition\n                for name in candidate_names[:max_tools]\n            ]",
      "section_ref": "6.3.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-9",
      "language": "text",
      "description": "当 LLM 判断多个工具调用之间没有依赖关系时，可以并行发起多个调用，显著提升效率。",
      "code": "用户: \"帮我查一下北京、上海、深圳的天气，然后计算三个城市的平均温度\"\n\n串行执行（慢）:\n  get_weather(北京) → 等待 → get_weather(上海) → 等待 → get_weather(深圳) → 等待 → calculator()\n\n并行执行（快）:\n  get_weather(北京) ──┐\n  get_weather(上海) ──┤ 同时执行\n  get_weather(深圳) ──┘\n          ↓\n      calculator()  ← 依赖前面三个结果",
      "section_ref": "6.4.1",
      "runnable": false,
      "dependencies": []
    },
    {
      "id": "code-10",
      "language": "python",
      "description": "",
      "code": "import asyncio\nfrom concurrent.futures import ThreadPoolExecutor\n\nclass ParallelToolExecutor:\n    \"\"\"并行工具执行器\"\"\"\n    \n    def __init__(self, registry: ToolRegistry, max_workers: int = 5):\n        self.registry = registry\n        self.max_workers = max_workers\n    \n    async def execute_parallel(\n        self,\n        tool_calls: list[dict]\n    ) -> list[dict]:\n        \"\"\"\n        并行执行多个工具调用\n        \n        tool_calls: [\n            {\n                \"id\": \"call_abc123\",\n                \"function\": {\n                    \"name\": \"get_weather\",\n                    \"arguments\": '{\"city\": \"北京\"}'\n                }\n            },\n            ...\n        ]\n        \"\"\"\n        semaphore = asyncio.Semaphore(self.max_workers)\n        \n        async def execute_one(call: dict) -> dict:\n            async with semaphore:\n                func_name = call[\"function\"][\"name\"]\n                func_args = json.loads(call[\"function\"][\"arguments\"])\n                \n                tool = self.registry.get(func_name)\n                if not tool:\n                    return {\n                        \"tool_call_id\": call[\"id\"],\n                        \"content\": f\"错误：未知工具 {func_name}\",\n                        \"status\": \"error\"\n                    }\n                \n                try:\n                    result = await asyncio.to_tool(\n                        tool.handler, **func_args\n                    )\n                    return {\n                        \"tool_call_id\": call[\"id\"],\n                        \"content\": json.dumps(result, ensure_ascii=False),\n                        \"status\": \"success\"\n                    }\n                except Exception as e:\n                    return {\n                        \"tool_call_id\": call[\"id\"],\n                        \"content\": f\"错误：{str(e)}\",\n                        \"status\": \"error\"\n                    }\n        \n        # 并发执行所有工具调用\n        results = await asyncio.gather(\n            *[execute_one(call) for call in tool_calls],\n            return_exceptions=True\n        )\n        \n        return [\n            r if isinstance(r, dict) else {\n                \"status\": \"error\",\n                \"content\": f\"执行异常：{str(r)}\"\n            }\n            for r in results\n        ]\n    \n    def analyze_dependencies(\n        self,\n        tool_calls: list[dict]\n    ) -> list[list[dict]]:\n        \"\"\"\n        分析工具调用之间的依赖关系，分组为可并行执行的批次\n        \n        返回：[batch1, batch2, ...]\n        batch1 中的所有调用可以并行，batch2 依赖 batch1 的结果\n        \"\"\"\n        # 简化实现：没有参数引用之前调用结果的，都可以并行\n        # 更复杂的实现需要分析参数间的数据流\n        \n        batches = []\n        current_batch = []\n        \n        for call in tool_calls:\n            args = json.loads(call[\"function\"][\"arguments\"])\n            depends_on_previous = False\n            \n            # 检查参数是否引用了之前调用的结果\n            for value in args.values():\n                if isinstance(value, str) and \"$\" in value:\n                    depends_on_previous = True\n                    break\n            \n            if depends_on_previous:\n                batches.append(current_batch)\n                current_batch = [call]\n            else:\n                current_batch.append(call)\n        \n        if current_batch:\n            batches.append(current_batch)\n        \n        return batches",
      "section_ref": "6.4.2",
      "runnable": true,
      "dependencies": [
        "concurrent"
      ]
    },
    {
      "id": "code-11",
      "language": "python",
      "description": "OpenAI API 原生支持并行工具调用：",
      "code": "response = client.chat.completions.create(\n    model=\"gpt-4o\",\n    messages=[{\"role\": \"user\", \"content\": \"北京和上海天气对比\"}],\n    tools=tools,\n    parallel_tool_calls=True  # 启用并行调用\n)\n\nmessage = response.choices[0].message\n\n# message.tool_calls 可能包含多个调用\nif message.tool_calls:\n    print(f\"LLM 请求了 {len(message.tool_calls)} 个并行调用\")\n    \n    # 工具调用1\n    call1 = message.tool_calls[0]\n    # call1.function.name = \"get_weather\"\n    # call1.function.arguments = '{\"city\": \"北京\"}'\n    \n    # 工具调用2\n    call2 = message.tool_calls[1]\n    # call2.function.name = \"get_weather\"\n    # call2.function.arguments = '{\"city\": \"上海\"}'\n    \n    # 并行执行两个调用，然后一次性返回所有结果\n    results = await parallel_executor.execute_parallel(message.tool_calls)\n    \n    # 将所有结果一起返回给 LLM\n    messages = [\n        {\"role\": \"user\", \"content\": \"北京和上海天气对比\"},\n        message.model_dump(),  # 包含 tool_calls 的 assistant 消息\n        *[\n            {\"role\": \"tool\", \"tool_call_id\": r[\"tool_call_id\"], \"content\": r[\"content\"]}\n            for r in results\n        ]\n    ]\n    \n    final_response = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=messages,\n        tools=tools\n    )",
      "section_ref": "6.4.3",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-12",
      "language": "python",
      "description": "",
      "code": "class ToolError(Exception):\n    \"\"\"工具错误基类\"\"\"\n    def __init__(self, message: str, recoverable: bool = True):\n        super().__init__(message)\n        self.recoverable = recoverable\n\nclass ToolNotFoundError(ToolError):\n    \"\"\"工具不存在\"\"\"\n    def __init__(self, tool_name: str):\n        super().__init__(f\"工具 '{tool_name}' 不存在\", recoverable=False)\n\nclass ToolExecutionError(ToolError):\n    \"\"\"工具执行失败\"\"\"\n    def __init__(self, tool_name: str, reason: str):\n        super().__init__(\n            f\"工具 '{tool_name}' 执行失败：{reason}\",\n            recoverable=True\n        )\n\nclass ToolTimeoutError(ToolError):\n    \"\"\"工具执行超时\"\"\"\n    def __init__(self, tool_name: str, timeout: float):\n        super().__init__(\n            f\"工具 '{tool_name}' 执行超时（{timeout}s）\",\n            recoverable=True\n        )\n\nclass ToolArgumentError(ToolError):\n    \"\"\"工具参数错误\"\"\"\n    def __init__(self, tool_name: str, reason: str):\n        super().__init__(\n            f\"工具 '{tool_name}' 参数错误：{reason}\",\n            recoverable=True\n        )\n\nclass ToolRateLimitError(ToolError):\n    \"\"\"工具调用频率超限\"\"\"\n    def __init__(self, tool_name: str, retry_after: float = 60.0):\n        super().__init__(\n            f\"工具 '{tool_name}' 频率超限，{retry_after}s 后重试\",\n            recoverable=True\n        )\n        self.retry_after = retry_after\n\nclass ToolPermissionError(ToolError):\n    \"\"\"权限不足\"\"\"\n    def __init__(self, tool_name: str, required_permission: str):\n        super().__init__(\n            f\"工具 '{tool_name}' 需要权限：{required_permission}\",\n            recoverable=False\n        )",
      "section_ref": "6.5.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-13",
      "language": "python",
      "description": "",
      "code": "import asyncio\nfrom functools import wraps\nfrom datetime import datetime, timedelta\n\nclass ToolErrorHandler:\n    \"\"\"工具错误处理器\"\"\"\n    \n    def __init__(self, max_retries: int = 3, base_delay: float = 1.0):\n        self.max_retries = max_retries\n        self.base_delay = base_delay\n        self._error_log: list[dict] = []\n    \n    async def safe_execute(\n        self,\n        tool: RegisteredTool,\n        args: dict\n    ) -> dict:\n        \"\"\"安全执行工具（带重试和降级）\"\"\"\n        \n        for attempt in range(self.max_retries + 1):\n            try:\n                # 检查超时\n                result = await asyncio.wait_for(\n                    asyncio.to_tool(tool.handler, **args),\n                    timeout=tool.meta.timeout_seconds\n                )\n                return {\"status\": \"success\", \"data\": result}\n            \n            except asyncio.TimeoutError:\n                self._log_error(tool.meta.name, \"TIMEOUT\", None)\n                if attempt < self.max_retries:\n                    await self._backoff(attempt)\n                    continue\n                return self._handle_timeout(tool)\n            \n            except ToolRateLimitError as e:\n                self._log_error(tool.meta.name, \"RATE_LIMIT\", str(e))\n                if attempt < self.max_retries:\n                    await asyncio.sleep(e.retry_after)\n                    continue\n                return {\"status\": \"error\", \"error\": \"频率超限，请稍后重试\"}\n            \n            except ToolArgumentError as e:\n                self._log_error(tool.meta.name, \"ARGUMENT_ERROR\", str(e))\n                return {\"status\": \"error\", \"error\": f\"参数错误：{e}\", \"recoverable\": True}\n            \n            except ToolPermissionError as e:\n                self._log_error(tool.meta.name, \"PERMISSION\", str(e))\n                return {\"status\": \"error\", \"error\": f\"权限不足：{e}\", \"recoverable\": False}\n            \n            except Exception as e:\n                self._log_error(tool.meta.name, \"UNKNOWN\", str(e))\n                if attempt < self.max_retries:\n                    await self._backoff(attempt)\n                    continue\n                return {\"status\": \"error\", \"error\": f\"未知错误：{e}\"}\n        \n        return {\"status\": \"error\", \"error\": \"重试次数已耗尽\"}\n    \n    def _backoff(self, attempt: int):\n        \"\"\"指数退避\"\"\"\n        delay = self.base_delay * (2 ** attempt)\n        import random\n        delay = delay * (0.5 + random.random())  # 添加随机抖动\n        return asyncio.sleep(delay)\n    \n    def _handle_timeout(self, tool: RegisteredTool) -> dict:\n        \"\"\"处理超时\"\"\"\n        return {\n            \"status\": \"error\",\n            \"error\": f\"工具 {tool.meta.name} 执行超时\",\n            \"suggestion\": \"尝试简化请求或稍后重试\",\n            \"recoverable\": True\n        }\n    \n    def _log_error(self, tool_name: str, error_type: str, message: str):\n        \"\"\"记录错误\"\"\"\n        self._error_log.append({\n            \"tool\": tool_name,\n            \"error_type\": error_type,\n            \"message\": message,\n            \"timestamp\": datetime.now().isoformat()\n        })\n    \n    def get_error_summary(self) -> dict:\n        \"\"\"获取错误摘要\"\"\"\n        if not self._error_log:\n            return {\"total_errors\": 0}\n        \n        from collections import Counter\n        type_counts = Counter(e[\"error_type\"] for e in self._error_log)\n        tool_counts = Counter(e[\"tool\"] for e in self._error_log)\n        \n        return {\n            \"total_errors\": len(self._error_log),\n            \"by_type\": dict(type_counts),\n            \"by_tool\": dict(tool_counts),\n            \"recent_errors\": self._error_log[-5:]\n        }",
      "section_ref": "6.5.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-14",
      "language": "python",
      "description": "",
      "code": "def format_error_for_llm(error_result: dict) -> str:\n    \"\"\"将错误信息格式化为 LLM 友好的文本\"\"\"\n    if error_result[\"status\"] == \"success\":\n        return json.dumps(error_result[\"data\"], ensure_ascii=False)\n    \n    error = error_result.get(\"error\", \"未知错误\")\n    recoverable = error_result.get(\"recoverable\", True)\n    suggestion = error_result.get(\"suggestion\", \"\")\n    \n    parts = [f\"工具调用失败：{error}\"]\n    \n    if recoverable:\n        parts.append(\"这是一个可恢复的错误。你可以：\")\n        parts.append(\"1. 检查参数是否正确，重新调用\")\n        parts.append(\"2. 尝试使用其他工具达到相同目的\")\n        parts.append(\"3. 基于已有信息给出回答\")\n    else:\n        parts.append(\"这是一个不可恢复的错误。请放弃此工具，尝试其他方案。\")\n    \n    if suggestion:\n        parts.append(f\"建议：{suggestion}\")\n    \n    return \"\\n\".join(parts)\n\n# 在 Agent 循环中使用\n# 当工具返回错误时，将格式化的错误信息作为 tool result 返回给 LLM\n# LLM 会根据错误信息决定是否重试或切换策略",
      "section_ref": "6.5.3",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-15",
      "language": "python",
      "description": "",
      "code": "from abc import ABC, abstractmethod\n\nclass BaseTool(ABC):\n    \"\"\"工具基类\"\"\"\n    \n    @property\n    @abstractmethod\n    def name(self) -> str:\n        \"\"\"工具名称\"\"\"\n        pass\n    \n    @property\n    @abstractmethod\n    def description(self) -> str:\n        \"\"\"工具描述\"\"\"\n        pass\n    \n    @property\n    @abstractmethod\n    def parameters_schema(self) -> dict:\n        \"\"\"参数 JSON Schema\"\"\"\n        pass\n    \n    @abstractmethod\n    def execute(self, **kwargs) -> Any:\n        \"\"\"执行工具\"\"\"\n        pass\n    \n    def to_openai_tool(self) -> dict:\n        \"\"\"转换为 OpenAI tools 格式\"\"\"\n        return {\n            \"type\": \"function\",\n            \"function\": {\n                \"name\": self.name,\n                \"description\": self.description,\n                \"parameters\": self.parameters_schema\n            }\n        }\n    \n    def validate_args(self, args: dict) -> dict:\n        \"\"\"验证参数\"\"\"\n        schema = self.parameters_schema\n        required = schema.get(\"required\", [])\n        properties = schema.get(\"properties\", {})\n        \n        for field in required:\n            if field not in args:\n                raise ToolArgumentError(\n                    self.name, f\"缺少必填参数：{field}\"\n                )\n        \n        for key, value in args.items():\n            if key not in properties:\n                raise ToolArgumentError(\n                    self.name, f\"未知参数：{key}\"\n                )\n        \n        return args",
      "section_ref": "6.6.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-16",
      "language": "python",
      "description": "",
      "code": "class CodeExecutionTool(BaseTool):\n    \"\"\"安全代码执行工具\"\"\"\n    \n    def __init__(\n        self,\n        timeout: float = 30.0,\n        max_output_length: int = 10000,\n        allowed_modules: list[str] | None = None\n    ):\n        self.timeout = timeout\n        self.max_output_length = max_output_length\n        self.allowed_modules = allowed_modules or [\n            \"math\", \"json\", \"re\", \"datetime\",\n            \"collections\", \"itertools\", \"statistics\"\n        ]\n    \n    @property\n    def name(self) -> str:\n        return \"execute_code\"\n    \n    @property\n    def description(self) -> str:\n        return \"\"\"执行 Python 代码并返回结果。\n        \n        支持标准库中的 math, json, re, datetime 等模块。\n        不支持网络请求、文件系统操作。\n        \n        使用场景：数学计算、数据处理、算法验证。\n        不适合：需要外部 API、文件操作的任务。\"\"\"\n    \n    @property\n    def parameters_schema(self) -> dict:\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"code\": {\n                    \"type\": \"string\",\n                    \"description\": \"要执行的 Python 代码\"\n                },\n                \"language\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"python\"],\n                    \"default\": \"python\",\n                    \"description\": \"编程语言（目前仅支持 Python）\"\n                }\n            },\n            \"required\": [\"code\"]\n        }\n    \n    def execute(self, code: str, language: str = \"python\") -> dict:\n        \"\"\"执行代码\"\"\"\n        import sys\n        from io import StringIO\n        import traceback\n        \n        # 安全检查\n        self._security_check(code)\n        \n        # 准备执行环境\n        old_stdout = sys.stdout\n        old_stderr = sys.stderr\n        stdout_capture = StringIO()\n        stderr_capture = StringIO()\n        \n        # 限制可用模块\n        safe_globals = {\n            \"__builtins__\": {\n                \"print\": print,\n                \"range\": range,\n                \"len\": len,\n                \"int\": int,\n                \"float\": float,\n                \"str\": str,\n                \"list\": list,\n                \"dict\": dict,\n                \"set\": set,\n                \"tuple\": tuple,\n                \"sorted\": sorted,\n                \"enumerate\": enumerate,\n                \"zip\": zip,\n                \"map\": map,\n                \"filter\": filter,\n                \"min\": min,\n                \"max\": max,\n                \"sum\": sum,\n                \"abs\": abs,\n                \"round\": round,\n                \"type\": type,\n                \"isinstance\": isinstance,\n                \"True\": True,\n                \"False\": False,\n                \"None\": None,\n            }\n        }\n        \n        # 加载允许的模块\n        for module_name in self.allowed_modules:\n            try:\n                safe_globals[module_name] = __import__(module_name)\n            except ImportError:\n                pass\n        \n        result = {\n            \"stdout\": \"\",\n            \"stderr\": \"\",\n            \"error\": None,\n            \"return_value\": None\n        }\n        \n        try:\n            sys.stdout = stdout_capture\n            sys.stderr = stderr_capture\n            \n            # 使用信号或线程实现超时\n            exec(code, safe_globals, safe_globals)\n            \n        except Exception as e:\n            result[\"error\"] = f\"{type(e).__name__}: {str(e)}\"\n            result[\"stderr\"] = traceback.format_exc()\n        finally:\n            sys.stdout = old_stdout\n            sys.stderr = old_stderr\n        \n        result[\"stdout\"] = stdout_capture.getvalue()[:self.max_output_length]\n        result[\"stderr\"] = stderr_capture.getvalue()[:self.max_output_length]\n        \n        return result\n    \n    def _security_check(self, code: str):\n        \"\"\"安全检查\"\"\"\n        dangerous_patterns = [\n            r'\\bimport\\s+(?!math|json|re|datetime|collections|itertools|statistics)',\n            r'\\b__import__\\b',\n            r'\\bos\\.',\n            r'\\bsubprocess\\b',\n            r'\\bexec\\b',\n            r'\\beval\\b',\n            r'\\bopen\\s*\\(',\n            r'\\bgetattr\\b',\n            r'\\bsetattr\\b',\n            r'\\bdelattr\\b',\n            r'\\bcompile\\b',\n            r'\\bglobals\\b',\n            r'\\blocals\\b',\n        ]\n        \n        import re\n        for pattern in dangerous_patterns:\n            if re.search(pattern, code):\n                raise ToolPermissionError(\n                    self.name,\n                    f\"代码包含不允许的操作：{pattern}\"\n                )",
      "section_ref": "6.6.2",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-17",
      "language": "python",
      "description": "",
      "code": "class APICallTool(BaseTool):\n    \"\"\"通用 API 调用工具\"\"\"\n    \n    def __init__(\n        self,\n        base_url: str,\n        default_headers: dict | None = None,\n        auth_token: str | None = None,\n        timeout: float = 15.0\n    ):\n        self.base_url = base_url.rstrip(\"/\")\n        self.default_headers = default_headers or {}\n        self.auth_token = auth_token\n        self.timeout = timeout\n    \n    @property\n    def name(self) -> str:\n        return \"api_call\"\n    \n    @property\n    def description(self) -> str:\n        return f\"\"\"调用 {self.base_url} 的 API 接口。\n        \n        支持 GET、POST、PUT、DELETE 方法。\n        自动处理认证和错误重试。\n        \n        使用场景：获取远程数据、提交表单、更新资源。\n        \"\"\"\n    \n    @property\n    def parameters_schema(self) -> dict:\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"method\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"GET\", \"POST\", \"PUT\", \"DELETE\"],\n                    \"default\": \"GET\",\n                    \"description\": \"HTTP 方法\"\n                },\n                \"endpoint\": {\n                    \"type\": \"string\",\n                    \"description\": \"API 端点路径，如 /users, /orders/123\"\n                },\n                \"params\": {\n                    \"type\": \"object\",\n                    \"description\": \"查询参数（GET 请求）\"\n                },\n                \"body\": {\n                    \"type\": \"object\",\n                    \"description\": \"请求体（POST/PUT 请求）\"\n                }\n            },\n            \"required\": [\"endpoint\"]\n        }\n    \n    def execute(\n        self,\n        endpoint: str,\n        method: str = \"GET\",\n        params: dict | None = None,\n        body: dict | None = None\n    ) -> dict:\n        \"\"\"执行 API 调用\"\"\"\n        import httpx\n        \n        url = f\"{self.base_url}{endpoint}\"\n        headers = {\n            **self.default_headers,\n            \"Content-Type\": \"application/json\"\n        }\n        \n        if self.auth_token:\n            headers[\"Authorization\"] = f\"Bearer {self.auth_token}\"\n        \n        try:\n            with httpx.Client(timeout=self.timeout) as client:\n                response = client.request(\n                    method=method,\n                    url=url,\n                    params=params,\n                    json=body,\n                    headers=headers\n                )\n                \n                result = {\n                    \"status_code\": response.status_code,\n                    \"data\": None,\n                    \"error\": None\n                }\n                \n                try:\n                    result[\"data\"] = response.json()\n                except Exception:\n                    result[\"data\"] = response.text\n                \n                if response.status_code >= 400:\n                    result[\"error\"] = f\"HTTP {response.status_code}: {response.text[:500]}\"\n                \n                return result\n                \n        except httpx.TimeoutException:\n            raise ToolTimeoutError(self.name, self.timeout)\n        except httpx.ConnectError as e:\n            raise ToolExecutionError(self.name, f\"连接失败：{e}\")",
      "section_ref": "6.6.3",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-18",
      "language": "python",
      "description": "有时一个高级功能需要组合多个基础工具：",
      "code": "class CompositeTool(BaseTool):\n    \"\"\"组合工具——将多个工具组合为一个高级功能\"\"\"\n    \n    def __init__(self, name: str, description: str, steps: list[dict]):\n        self._name = name\n        self._description = description\n        self.steps = steps\n        self._registry: ToolRegistry | None = None\n    \n    def set_registry(self, registry: ToolRegistry):\n        self._registry = registry\n    \n    @property\n    def name(self) -> str:\n        return self._name\n    \n    @property\n    def description(self) -> str:\n        return self._description\n    \n    @property\n    def parameters_schema(self) -> dict:\n        # 从所有步骤中提取输入参数\n        input_params = {}\n        for step in self.steps:\n            if step.get(\"type\") == \"input\":\n                for param_name, param_schema in step.get(\"params\", {}).items():\n                    input_params[param_name] = param_schema\n        \n        return {\n            \"type\": \"object\",\n            \"properties\": input_params,\n            \"required\": [\n                k for k, v in input_params.items()\n                if not v.get(\"default\")\n            ]\n        }\n    \n    def execute(self, **kwargs) -> Any:\n        \"\"\"按步骤执行组合工具\"\"\"\n        context = dict(kwargs)  # 共享上下文\n        \n        for i, step in enumerate(self.steps):\n            step_type = step[\"type\"]\n            \n            if step_type == \"tool_call\":\n                tool_name = step[\"tool\"]\n                tool = self._registry.get(tool_name)\n                if not tool:\n                    raise ToolNotFoundError(tool_name)\n                \n                # 从上下文中填充参数\n                args = {}\n                for param_name, param_source in step.get(\"args\", {}).items():\n                    if isinstance(param_source, str) and param_source.startswith(\"$\"):\n                        # 引用上下文变量\n                        var_name = param_source[1:]\n                        args[param_name] = context[var_name]\n                    else:\n                        args[param_name] = param_source\n                \n                result = tool.execute(**args)\n                context[f\"step_{i}_result\"] = result\n                \n                if isinstance(result, dict) and result.get(\"status\") == \"error\":\n                    return result\n                \n            elif step_type == \"transform\":\n                # 数据转换步骤\n                source = step[\"source\"]\n                if source.startswith(\"$\"):\n                    context[f\"step_{i}_result\"] = context[source[1:]]\n            \n            elif step_type == \"output\":\n                # 最终输出\n                source = step[\"source\"]\n                if source.startswith(\"$\"):\n                    return context[source[1:]]\n        \n        return context.get(\"step_result\", \"执行完成\")\n\n\n# 示例：创建一个\"获取城市信息\"的组合工具\ncity_info_tool = CompositeTool(\n    name=\"get_city_info\",\n    description=\"获取城市的综合信息，包括天气、人口、景点等\",\n    steps=[\n        {\"type\": \"input\", \"params\": {\n            \"city\": {\"type\": \"string\", \"description\": \"城市名称\"}\n        }},\n        {\"type\": \"tool_call\", \"tool\": \"get_weather\", \"args\": {\"city\": \"$city\"}},\n        {\"type\": \"tool_call\", \"tool\": \"search_wiki\", \"args\": {\"query\": \"$city\"}},\n        {\"type\": \"output\", \"source\": \"$step_2_result\"}\n    ]\n)",
      "section_ref": "6.6.4",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-19",
      "language": "python",
      "description": "",
      "code": "# ❌ 定义与实际实现不一致\ndefinition = {\n    \"name\": \"get_user\",\n    \"parameters\": {\"properties\": {\"user_id\": {\"type\": \"string\"}}}\n}\n\n# 但实际函数接受的参数是 \"id\" 而不是 \"user_id\"\ndef get_user(id: int):  # 类型也不一致！\n    ...\n\n# ✅ 确保定义与实现一致\n# 使用前面介绍的 function_to_tool 自动生成",
      "section_ref": "6.7.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-20",
      "language": "python",
      "description": "",
      "code": "# ❌ 没有确认机制的破坏性操作\ndef delete_all_records(table: str):\n    db.execute(f\"DELETE FROM {table}\")  # 直接删除！\n\n# ✅ 添加确认机制和审计日志\ndef delete_records(table: str, condition: str, dry_run: bool = True):\n    if dry_run:\n        return preview_deletion(table, condition)\n    else:\n        audit_log(f\"DELETE from {table} where {condition}\")\n        return actual_delete(table, condition)",
      "section_ref": "6.7.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-21",
      "language": "python",
      "description": "",
      "code": "# ❌ 返回整个数据库记录\ndef search_products(query: str):\n    return db.query(\"SELECT * FROM products WHERE name LIKE ?\", f\"%{query}%\")\n    # 可能返回数千条记录，消耗大量 Token\n\n# ✅ 限制返回数量，只返回必要字段\ndef search_products(query: str, limit: int = 5):\n    results = db.query(\n        \"SELECT id, name, price FROM products WHERE name LIKE ? LIMIT ?\",\n        f\"%{query}%\", limit\n    )\n    return results",
      "section_ref": "6.7.1",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-22",
      "language": "python",
      "description": "",
      "code": "# 完整的工具开发检查清单\nTOOL_DEVELOPMENT_CHECKLIST = \"\"\"\n## 工具开发最佳实践清单\n\n### ✅ 设计\n- [ ] 工具职责单一（Single Responsibility）\n- [ ] 描述清晰，包含适用/不适用场景\n- [ ] 参数命名符合直觉，有完整的描述\n- [ ] 有合理的默认值\n\n### ✅ 安全\n- [ ] 输入验证（类型、范围、格式）\n- [ ] SQL注入/XSS 防护\n- [ ] 破坏性操作需要确认机制\n- [ ] 资源访问权限检查\n- [ ] 敏感数据脱敏\n\n### ✅ 可靠性\n- [ ] 超时保护\n- [ ] 重试机制（带指数退避）\n- [ ] 错误信息对 LLM 友好\n- [ ] 返回结果大小可控\n\n### ✅ 可观测性\n- [ ] 调用日志记录\n- [ ] 执行耗时监控\n- [ ] 错误率统计\n- [ ] 审计追踪\n\n### ✅ 测试\n- [ ] 单元测试覆盖正常/异常路径\n- [ ] 集成测试验证端到端流程\n- [ ] LLM 调用准确性测试\n\"\"\"",
      "section_ref": "6.7.2",
      "runnable": true,
      "dependencies": []
    }
  ],
  "tables": [
    {
      "headers": [
        "提供商",
        "API",
        "特点"
      ],
      "data": [
        [
          "OpenAI",
          "`tools` 参数",
          "最成熟，支持并行调用"
        ],
        [
          "Anthropic",
          "`tool_choice` + `tools`",
          "支持 cache_control 优化"
        ],
        [
          "Google Gemini",
          "`function_declarations`",
          "支持原生 Python 函数导入"
        ],
        [
          "Anthropic Bedrock",
          "同 Anthropic",
          "AWS 集成"
        ]
      ]
    }
  ],
  "key_takeaways": [],
  "common_pitfalls": [],
  "related_chapters": [
    "ch04",
    "ch05",
    "ch08",
    "ch13",
    "ch16",
    "ch17",
    "ch18",
    "ch19",
    "ch22",
    "ch26",
    "ch34"
  ]
}