第 14 章

工具生态:内置工具深解与自定义工具开发

第十四章:工具生态:内置工具深解与自定义工具开发

全面掌握 Dify 工具生态系统——从内置工具的完整配置到自定义工具的开发、测试与生产化,让 Agent 能力边界由你定义。

本章导读

Dify Agent 的能力边界由工具决定。没有工具,Agent 只是一个聪明的聊天机器人;有了合适的工具,Agent 才能真正解决业务问题。本章从两个维度展开:一是系统梳理 Dify 的内置工具(Web 搜索、代码执行、知识库检索等)的配置与调优;二是完整演示如何开发、测试和发布自定义工具,包括 API 封装、认证处理和错误设计。

读完本章,你将能够:


Level 1:基础认知(1-3 年经验)

什么是 Dify 工具?

在 Dify 中,"工具"是 Agent 可以调用的外部能力单元。每个工具有:

工具的描述质量至关重要。如果描述不清晰,LLM 可能不知道什么时候该用这个工具,或者误用它。

Dify 内置工具分类

Dify 提供了丰富的内置工具,分为几大类:

搜索类工具:

代码执行类:

知识库类:

效率工具类:

图像类:

如何在 Dify 中启用和配置内置工具

  1. 进入 Dify 控制台 → 工具 → 内置工具
  2. 找到目标工具,点击"配置"
  3. 填入必要的 API Key(如搜索工具需要 SerpAPI Key)
  4. 在 Agent 应用中选择该工具

以 Web 搜索工具为例:

# 工具配置(通过 Dify 控制台填写)
tool: web_search
provider: serpapi
config:
  api_key: "your_serpapi_key"
  gl: "cn"        # 国家/地区:中国
  hl: "zh-CN"     # 语言:中文
  num: 5          # 每次搜索返回 5 条结果

第一个自定义工具:Hello World

在 Dify 中,自定义工具通过"自定义 API"功能添加,本质上是把外部 REST API 包装成工具:

# 简单的自定义工具:查询汇率
openapi: 3.1.0
info:
  title: 汇率查询工具
  version: 1.0.0
servers:
  - url: https://api.exchangerate.host

paths:
  /latest:
    get:
      operationId: get_exchange_rate
      summary: 获取最新汇率
      description: 查询指定货币对的最新汇率,用于货币换算
      parameters:
        - name: base
          in: query
          required: true
          description: 基准货币代码,如 USD、CNY、EUR
          schema:
            type: string
        - name: symbols
          in: query
          required: false
          description: 目标货币代码,逗号分隔,如 CNY,EUR,JPY
          schema:
            type: string

把这个 OpenAPI 规范粘贴到 Dify 的"自定义 API 工具"配置框,Dify 会自动解析并生成工具描述。


Level 2:机制深解(3-5 年经验)

内置工具深解:code_interpreter

代码解释器是 Dify 中最强大也最复杂的内置工具。它在一个隔离的 Python 沙箱环境中执行代码,支持:

代码解释器的底层实现:

Dify 的代码解释器使用容器化沙箱,每次执行都在独立容器中运行:

# 代码解释器工具的调用示例
# Agent 生成的代码
code = """
import pandas as pd
import matplotlib.pyplot as plt
import io, base64

# 分析销售数据
data = {
    '月份': ['1月', '2月', '3月', '4月', '5月'],
    '销售额': [120000, 135000, 98000, 156000, 178000]
}
df = pd.DataFrame(data)

# 计算统计数据
print(f"总销售额: {df['销售额'].sum():,} 元")
print(f"月均销售: {df['销售额'].mean():,.0f} 元")
print(f"最高月份: {df.loc[df['销售额'].idxmax(), '月份']}")
print(f"增长趋势: {((df['销售额'].iloc[-1] / df['销售额'].iloc[0]) - 1) * 100:.1f}%")

# 生成图表
fig, ax = plt.subplots(figsize=(8, 5))
ax.bar(df['月份'], df['销售额'] / 10000, color='steelblue', alpha=0.8)
ax.set_title('月度销售额趋势', fontsize=14)
ax.set_ylabel('销售额(万元)')
plt.tight_layout()

# 转为 base64 返回
buf = io.BytesIO()
plt.savefig(buf, format='png', dpi=100)
buf.seek(0)
img_b64 = base64.b64encode(buf.read()).decode()
print(f"[图表]\ndata:image/png;base64,{img_b64}")
"""

代码解释器的安全限制(不允许的操作):

# 以下操作在沙箱中被禁止
import subprocess  # ❌ 禁止执行系统命令
import os          # ❌ 禁止访问文件系统(部分限制)
import socket      # ❌ 禁止网络访问
import requests    # ❌ 禁止 HTTP 请求(在某些配置下)

# 允许的操作
import pandas as pd    # ✅
import numpy as np     # ✅
import matplotlib      # ✅
import scipy           # ✅
import sklearn         # ✅
# 内置 Python 库大多可用

使用代码解释器的最佳实践:

# 在 Agent 提示词中引导模型正确使用代码解释器
AGENT_SYSTEM_PROMPT = """
你是一名数据分析助手。当用户请求数据分析时:

1. 使用 code_interpreter 工具执行 Python 代码
2. 代码必须打印出关键数字(不要只生成图表)
3. 统计分析后,用自然语言解释结果含义
4. 如果用户上传了文件,用 pd.read_csv('/workspace/uploads/filename.csv') 读取

注意:代码中避免使用 print 以外的输出方式,确保结果可见。
"""

内置工具深解:dataset_retrieval(知识库检索)

这个工具是 Dify RAG 能力在 Agent 场景的延伸。与普通问答应用不同,Agent 可以主动决定"什么时候需要检索知识库"以及"用什么关键词检索":

# Agent 使用知识库检索的示例推理
"""
用户:我们公司的年假政策是怎样的?

思考:用户询问公司年假政策,这是内部 HR 知识,应该从知识库中检索。
行动:dataset_retrieval
行动输入:{
  "dataset_ids": ["hr-policy-kb-001"],
  "query": "年假政策 休假天数 申请流程"
}
观察:[检索结果]
  相关度 0.92:员工年假政策(2024版)
  工作满1年但不满10年的员工,享有5天年假...
  
思考:找到了相关政策,可以回答用户。
最终答案:根据公司 HR 政策,工作满 1 年不满 10 年的员工享有...
"""

知识库检索工具的参数优化:

# 知识库检索工具的高级配置
tool: dataset_retrieval
config:
  top_k: 5              # 返回最相关的 5 个片段
  score_threshold: 0.6  # 相似度阈值,低于此值不返回
  search_method: hybrid # hybrid(混合)= 语义 + 关键词
  reranking: true       # 启用重排序提升精度

自定义工具开发:完整实战

步骤一:设计工具接口

好的工具接口设计要遵循以下原则:

  1. 单一职责:一个工具做一件事
  2. 描述精确:描述要告诉 LLM 工具的适用场景
  3. 参数最小化:只暴露必要参数,可选参数给默认值
  4. 错误可读:错误信息要让 LLM 能理解并自我修正
# 示例:企业内部 CRM 查询工具

tool_definition = {
    "name": "query_customer_info",
    "description": (
        "从 CRM 系统查询客户信息。当用户询问特定客户的基本信息、"
        "联系方式、购买历史或合同状态时使用此工具。"
        "注意:此工具仅支持按客户名称或客户ID精确查询,不支持模糊搜索。"
    ),
    "parameters": {
        "type": "object",
        "properties": {
            "customer_id": {
                "type": "string",
                "description": "客户唯一ID,格式为 'CUST-' 加6位数字,如 'CUST-001234'"
            },
            "customer_name": {
                "type": "string",
                "description": "客户公司名称(与 customer_id 二选一)"
            },
            "fields": {
                "type": "array",
                "items": {"type": "string"},
                "description": "需要返回的字段,可选:contact, contract, purchase_history, credit_score",
                "default": ["contact", "contract"]
            }
        },
        "required": []  # 两个查询方式都是可选的,但至少需要一个
    }
}

步骤二:实现工具后端(Python 示例)

# tools/crm_tool.py
import httpx
import json
from typing import Optional, List
from pydantic import BaseModel, validator

class CRMQueryInput(BaseModel):
    customer_id:   Optional[str] = None
    customer_name: Optional[str] = None
    fields:        List[str]     = ["contact", "contract"]

    @validator("customer_id")
    def validate_customer_id(cls, v):
        if v and not v.startswith("CUST-"):
            raise ValueError(f"customer_id 格式错误,应为 CUST-XXXXXX,收到: {v}")
        return v

    def model_post_init(self, *args):
        if not self.customer_id and not self.customer_name:
            raise ValueError("customer_id 和 customer_name 至少提供一个")

ALLOWED_FIELDS = {"contact", "contract", "purchase_history", "credit_score"}

class CRMTool:
    def __init__(self, crm_base_url: str, api_key: str):
        self.base_url = crm_base_url
        self.client   = httpx.AsyncClient(
            base_url=crm_base_url,
            headers={"Authorization": f"Bearer {api_key}"},
            timeout=10.0
        )

    async def query(self, raw_params: dict) -> str:
        """工具入口,返回字符串供 LLM 理解"""
        try:
            params = CRMQueryInput(**raw_params)
        except ValueError as e:
            return f"参数错误:{e}\n请检查参数格式后重试。"

        # 过滤非法字段
        valid_fields = [f for f in params.fields if f in ALLOWED_FIELDS]

        try:
            if params.customer_id:
                resp = await self.client.get(
                    f"/api/customers/{params.customer_id}",
                    params={"fields": ",".join(valid_fields)}
                )
            else:
                resp = await self.client.get(
                    "/api/customers/search",
                    params={"name": params.customer_name,
                            "fields": ",".join(valid_fields)}
                )

            if resp.status_code == 404:
                return f"未找到客户信息。请确认客户ID或名称是否正确。"
            if resp.status_code == 403:
                return "权限不足,无法查询此客户信息。请联系管理员。"

            resp.raise_for_status()
            data = resp.json()

            # 将结果格式化为可读文本
            return self._format_result(data)

        except httpx.TimeoutException:
            return "CRM 系统响应超时(>10秒),请稍后重试。"
        except httpx.HTTPStatusError as e:
            return f"CRM 查询失败:HTTP {e.response.status_code}"

    def _format_result(self, data: dict) -> str:
        """把 JSON 数据格式化为 LLM 友好的文本"""
        lines = [f"客户:{data.get('name', '未知')}(ID: {data.get('id')})"]

        if "contact" in data:
            c = data["contact"]
            lines.append(f"联系人:{c.get('name')} / {c.get('phone')} / {c.get('email')}")

        if "contract" in data:
            ct = data["contract"]
            lines.append(f"合同状态:{ct.get('status')} / 到期日:{ct.get('expire_date')}")
            lines.append(f"合同金额:{ct.get('amount'):,} 元")

        if "credit_score" in data:
            lines.append(f"信用评分:{data['credit_score']}/100")

        return "\n".join(lines)

步骤三:OpenAPI 规范编写

Dify 通过 OpenAPI 规范导入自定义工具,规范文件必须完整:

openapi: 3.1.0
info:
  title: CRM 客户查询工具
  description: 从企业 CRM 系统查询客户信息
  version: 1.2.0

servers:
  - url: https://crm-api.company.internal
    description: 企业内部 CRM API

security:
  - BearerAuth: []

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

paths:
  /api/customers/{customer_id}:
    get:
      operationId: query_customer_by_id
      summary: 按 ID 查询客户信息
      description: |
        根据客户唯一ID精确查询客户信息。
        适用于:已知客户ID的精确查询场景。
      parameters:
        - name: customer_id
          in: path
          required: true
          description: 客户唯一ID,格式 CUST-XXXXXX
          schema:
            type: string
            pattern: '^CUST-\d{6}$'
        - name: fields
          in: query
          description: 需要返回的字段(逗号分隔)
          schema:
            type: string
            enum: [contact, contract, purchase_history, credit_score]
      responses:
        '200':
          description: 查询成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerInfo'
        '404':
          description: 客户不存在
        '403':
          description: 权限不足

  /api/customers/search:
    get:
      operationId: search_customer_by_name
      summary: 按名称搜索客户
      parameters:
        - name: name
          in: query
          required: true
          description: 客户公司名称(支持部分匹配)
          schema:
            type: string

components:
  schemas:
    CustomerInfo:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        contact:
          type: object
          properties:
            name:  {type: string}
            phone: {type: string}
            email: {type: string}
        contract:
          type: object
          properties:
            status:      {type: string}
            expire_date: {type: string}
            amount:      {type: number}

步骤四:在 Dify 中注册工具

  1. 进入 Dify 控制台 → 工具 → 自定义工具 → 创建工具
  2. 填写工具基本信息(名称、描述)
  3. 选择认证方式(Bearer Token、API Key、OAuth 等)
  4. 粘贴 OpenAPI 规范
  5. 点击"测试"验证工具可用性
  6. 保存并在 Agent 应用中启用
# 通过 Dify API 编程式注册工具(适合 CI/CD)
import httpx

DIFY_ADMIN_API = "https://your-dify.com/console/api"
ADMIN_TOKEN    = "your_admin_token"

def register_tool(tool_spec: dict) -> dict:
    resp = httpx.post(
        f"{DIFY_ADMIN_API}/workspaces/tools",
        headers={"Authorization": f"Bearer {ADMIN_TOKEN}"},
        json={
            "name":          tool_spec["name"],
            "description":   tool_spec["description"],
            "openapi_spec":  tool_spec["openapi"],
            "auth_type":     "bearer",
            "auth_config":   {"token": tool_spec["api_key"]}
        }
    )
    resp.raise_for_status()
    return resp.json()

Level 3:源码与原理(5 年以上)

Dify 工具系统的内部架构

Dify 的工具系统位于 api/core/tools/ 目录:

api/core/tools/
├── tool.py                     # 工具基类
├── tool_engine.py              # 工具执行引擎
├── tool_manager.py             # 工具管理器(注册、发现、权限)
├── builtin/                    # 内置工具实现
│   ├── web_search/
│   │   ├── _assets/            # 工具图标等资源
│   │   ├── tools/
│   │   │   └── google_search.py
│   │   └── web_search.yaml     # 工具描述文件
│   ├── code_interpreter/
│   └── ...
├── api_tools/                  # 自定义 API 工具
│   ├── api_tool.py             # API 工具基类
│   └── api_tool_bundle.py      # API 工具集合管理
└── provider/                   # 工具提供商管理

工具基类 Tool 的核心设计:

# api/core/tools/tool.py(核心接口)
from abc import ABC, abstractmethod
from typing import Any, Generator, Optional, Union
from pydantic import BaseModel

class ToolInvokeMessage(BaseModel):
    """工具执行结果的消息格式"""
    type: str  # "text" | "image" | "link" | "blob"
    message: Any
    meta: Optional[dict] = None

class Tool(ABC):
    """所有工具的抽象基类"""

    @property
    @abstractmethod
    def identity(self) -> ToolIdentity:
        """工具唯一标识(名称、作者、标签)"""
        raise NotImplementedError

    @property
    @abstractmethod
    def parameters(self) -> list[ToolParameter]:
        """工具参数定义"""
        raise NotImplementedError

    @abstractmethod
    def _invoke(
        self,
        user_id: str,
        tool_parameters: dict[str, Any],
    ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage], Generator]:
        """工具的实际执行逻辑"""
        raise NotImplementedError

    def invoke(
        self,
        user_id: str,
        tool_parameters: dict[str, Any],
    ) -> list[ToolInvokeMessage]:
        """
        公开的工具调用入口,封装了错误处理和验证
        """
        # 1. 参数验证
        validated = self._validate_parameters(tool_parameters)
        
        # 2. 执行工具
        result = self._invoke(user_id, validated)
        
        # 3. 标准化返回格式
        if isinstance(result, Generator):
            return list(result)
        elif isinstance(result, list):
            return result
        else:
            return [result]

    def _validate_parameters(self, params: dict) -> dict:
        """验证参数合法性"""
        validated = {}
        for param_def in self.parameters:
            value = params.get(param_def.name)
            if param_def.required and value is None:
                raise ValueError(f"缺少必需参数: {param_def.name}")
            if value is not None:
                # 类型转换和验证
                validated[param_def.name] = param_def.validate_value(value)
            elif param_def.default is not None:
                validated[param_def.name] = param_def.default
        return validated

内置工具的实现示例(Google 搜索):

# api/core/tools/builtin/web_search/tools/google_search.py
from serpapi import GoogleSearch as SerpApiGoogleSearch
from core.tools.tool.builtin_tool import BuiltinTool

class GoogleSearchTool(BuiltinTool):

    def _invoke(self, user_id: str, tool_parameters: dict):
        query   = tool_parameters.get("query", "")
        num     = tool_parameters.get("num_results", 5)
        gl      = tool_parameters.get("gl", "us")
        hl      = tool_parameters.get("hl", "en")

        # 调用 SerpAPI
        search_params = {
            "q":       query,
            "num":     num,
            "gl":      gl,
            "hl":      hl,
            "api_key": self.runtime.credentials.get("serpapi_api_key"),
        }

        try:
            results = SerpApiGoogleSearch(search_params).get_dict()
        except Exception as e:
            return self.create_text_message(f"搜索失败:{str(e)}")

        # 提取有机搜索结果
        organic = results.get("organic_results", [])
        if not organic:
            return self.create_text_message("没有找到相关结果")

        # 格式化结果
        output_lines = []
        for i, result in enumerate(organic[:num], 1):
            title   = result.get("title", "")
            snippet = result.get("snippet", "")
            link    = result.get("link", "")
            output_lines.append(f"{i}. **{title}**\n{snippet}\n链接:{link}")

        return self.create_text_message("\n\n".join(output_lines))

工具执行引擎的并发控制

Dify 的工具执行引擎支持并发执行多个工具(Function Calling 模式下):

# api/core/tools/tool_engine.py(并发执行逻辑)
import asyncio
from typing import Coroutine

class ToolEngine:

    async def invoke_tools_parallel(
        self,
        tool_calls: list[ToolCall],
        user_id: str,
    ) -> list[ToolInvokeResult]:
        """
        并行执行多个工具调用
        带超时控制和错误隔离
        """
        TOOL_TIMEOUT = 30  # 单个工具超时秒数

        async def invoke_single(tc: ToolCall) -> ToolInvokeResult:
            try:
                tool   = self.get_tool(tc.tool_name)
                result = await asyncio.wait_for(
                    asyncio.get_event_loop().run_in_executor(
                        None, tool.invoke, user_id, tc.parameters
                    ),
                    timeout=TOOL_TIMEOUT
                )
                return ToolInvokeResult(
                    tool_call_id=tc.id,
                    output=result,
                    success=True
                )
            except asyncio.TimeoutError:
                return ToolInvokeResult(
                    tool_call_id=tc.id,
                    output="工具执行超时",
                    success=False
                )
            except Exception as e:
                return ToolInvokeResult(
                    tool_call_id=tc.id,
                    output=f"工具执行错误:{str(e)}",
                    success=False
                )

        # 并行执行所有工具
        tasks   = [invoke_single(tc) for tc in tool_calls]
        results = await asyncio.gather(*tasks, return_exceptions=False)
        return list(results)

自定义工具的认证机制深解

Dify 支持多种认证方式,底层实现如下:

# 认证方式的内部处理
class APIToolAuthManager:

    def inject_auth(
        self,
        request: httpx.Request,
        auth_config: dict,
    ) -> httpx.Request:
        """根据认证配置注入认证信息"""
        auth_type = auth_config.get("type")

        if auth_type == "api_key":
            location = auth_config.get("in", "header")
            key_name = auth_config.get("name", "X-API-Key")
            key_val  = auth_config.get("value")

            if location == "header":
                request.headers[key_name] = key_val
            elif location == "query":
                # 将 API Key 加入查询参数
                url = httpx.URL(str(request.url))
                url = url.copy_with(
                    params={**dict(url.params), key_name: key_val}
                )
                request = request.copy_with(url=url)

        elif auth_type == "bearer":
            token = auth_config.get("token")
            request.headers["Authorization"] = f"Bearer {token}"

        elif auth_type == "oauth2":
            # OAuth2 需要先获取访问令牌
            access_token = self._get_oauth2_token(auth_config)
            request.headers["Authorization"] = f"Bearer {access_token}"

        elif auth_type == "basic":
            import base64
            username = auth_config.get("username")
            password = auth_config.get("password")
            creds    = base64.b64encode(f"{username}:{password}".encode()).decode()
            request.headers["Authorization"] = f"Basic {creds}"

        return request

    def _get_oauth2_token(self, auth_config: dict) -> str:
        """获取 OAuth2 访问令牌(带缓存)"""
        cache_key = f"oauth2_token_{auth_config['client_id']}"
        cached    = self.token_cache.get(cache_key)
        if cached and not self._is_expired(cached):
            return cached["access_token"]

        resp = httpx.post(
            auth_config["token_url"],
            data={
                "grant_type":    "client_credentials",
                "client_id":     auth_config["client_id"],
                "client_secret": auth_config["client_secret"],
            }
        )
        resp.raise_for_status()
        token_data = resp.json()

        # 缓存令牌(减少令牌申请频率)
        self.token_cache.set(
            cache_key,
            token_data,
            ttl=token_data.get("expires_in", 3600) - 60  # 提前 60s 过期
        )
        return token_data["access_token"]

Level 4:生产陷阱与决策(专家视角)

陷阱一:工具描述不精确导致的误调用

工具描述是 LLM 决定"何时调用哪个工具"的唯一依据。描述不精确会导致:

反例(描述太模糊):

name: data_query
description: 查询数据

正例(描述包含适用场景和限制):

name: crm_customer_query
description: |
  从 CRM 系统查询客户的基本信息、联系方式和合同状态。
  适用场景:用户询问特定客户信息、需要跟进哪些客户合同、
            查看某客户的购买历史。
  不适用场景:查询产品信息、库存数据、财务报表(这些请使用其他工具)。
  查询方式:支持按客户ID(格式:CUST-XXXXXX)或公司名称精确查询。

陷阱二:工具返回值太长超出上下文

当工具返回大量数据时(如搜索到 20 篇文章、查询到 100 条记录),会迅速填满 LLM 的上下文窗口,导致性能下降甚至报错。

解决方案:工具输出截断与摘要

class OutputTruncator:
    """工具输出截断器"""

    MAX_CHARS = 2000  # 工具输出最大字符数

    def truncate(self, output: str, strategy: str = "smart") -> str:
        if len(output) <= self.MAX_CHARS:
            return output

        if strategy == "tail":
            # 截断尾部(适合日志类输出)
            return output[:self.MAX_CHARS] + f"\n...[已截断,原始长度 {len(output)} 字符]"

        elif strategy == "smart":
            # 智能截断:保留开头和结尾
            half = self.MAX_CHARS // 2
            return (
                output[:half]
                + f"\n\n...[中间省略 {len(output) - self.MAX_CHARS} 字符]...\n\n"
                + output[-half:]
            )

        elif strategy == "summarize":
            # 使用 LLM 摘要(成本较高,但效果最好)
            return self._summarize_with_llm(output)

    def _summarize_with_llm(self, long_output: str) -> str:
        prompt = f"""以下是一段工具执行结果,请提取最重要的信息(不超过500字):

{long_output[:5000]}

请提取关键信息:"""
        return cheap_llm.call(prompt)  # 用便宜的小模型做摘要

陷阱三:工具调用的幂等性问题

Agent 在遇到错误时可能会重试工具调用,如果工具不幂等(如发送邮件、写数据库),可能造成重复操作:

# 幂等键机制
import hashlib, json

class IdempotentToolWrapper:
    """给非幂等工具添加幂等保护"""

    def __init__(self, tool, idempotency_store, ttl: int = 3600):
        self.tool   = tool
        self.store  = idempotency_store  # Redis 或数据库
        self.ttl    = ttl

    def invoke(self, user_id: str, params: dict) -> str:
        # 生成幂等键(基于用户、工具名、参数)
        key_data  = json.dumps({"user": user_id, "tool": self.tool.name, "params": params},
                               sort_keys=True)
        idem_key  = f"idem:{hashlib.sha256(key_data.encode()).hexdigest()}"

        # 检查是否已执行过
        cached = self.store.get(idem_key)
        if cached:
            return f"[已去重] {cached}"

        # 执行工具
        result = self.tool.invoke(user_id, params)
        result_str = str(result)

        # 缓存结果(TTL 内不重复执行)
        self.store.setex(idem_key, self.ttl, result_str)
        return result_str

工具性能基准与优化建议

基于生产环境实测数据:

工具类型 平均延迟 P99 延迟 主要瓶颈 优化方向
Web 搜索(SerpAPI) 1.2s 3.5s 外部 API 缓存热门查询
代码解释器 2.8s 8s 容器启动+执行 预热容器池
知识库检索 0.3s 0.8s 向量数据库 预加载热门 KB
自定义 API 工具 0.5-5s 不定 目标 API 性能 设合理超时
Calculator <10ms <50ms 本地计算 无需优化

工具缓存实现(减少重复调用成本):

import functools, hashlib, json
from datetime import timedelta

def cacheable_tool(ttl: int = 300):
    """工具结果缓存装饰器"""
    def decorator(invoke_func):
        @functools.wraps(invoke_func)
        async def wrapper(self, user_id: str, params: dict) -> str:
            # 生成缓存键
            cache_key = f"tool_cache:{self.name}:{hashlib.md5(json.dumps(params, sort_keys=True).encode()).hexdigest()}"

            # 检查缓存
            cached = await redis.get(cache_key)
            if cached:
                return cached.decode()

            # 执行工具
            result = await invoke_func(self, user_id, params)

            # 缓存结果
            await redis.setex(cache_key, ttl, str(result))
            return result
        return wrapper
    return decorator

class WeatherTool(Tool):
    @cacheable_tool(ttl=600)  # 天气缓存 10 分钟
    async def _invoke(self, user_id: str, params: dict) -> str:
        city = params["city"]
        resp = await weather_api.get(city)
        return f"{city}天气:{resp['weather']},{resp['temperature']}°C"

本章小结

本章系统介绍了 Dify 工具生态的全貌,从内置工具的深度配置到自定义工具的完整开发链路:

核心要点:

  1. 工具描述是 LLM 选择工具的唯一依据——描述必须包含适用场景和限制,不能只有一句话
  2. 代码解释器是处理数据分析任务的最强工具,在沙箱中安全执行 Python,支持 pandas/matplotlib
  3. 自定义工具开发四步法:设计接口 → 实现后端 → 编写 OpenAPI 规范 → 在 Dify 注册
  4. 工具认证支持 API Key、Bearer Token、OAuth2、Basic Auth,由 Dify 的认证管理器自动注入

生产关键点:

关键数字:

下一章预告: 第 15 章将进入多 Agent 协作的世界,探讨 Dify Workflow 如何编排多个 Agent 协同完成复杂任务。

本章评分
4.6  / 5  (19 评分)

💬 留言讨论