开发外部 API 聚合与调用工具插件

约 35 分钟

使用 ctx.tools 注册模型可见的第三方 REST API 聚合工具,完成结构化入参校验与结果封装。

上一课我们系统梳理了插件挂载过程中的典型异常与诊断方案:从 inject 依赖未满足导致的挂死,到 Schemastery 配置校验失败,再到重复 loader entry id 冲突的排查,最终借助启动日志与 Creator 模式确认了插件拓扑树健康可用。插件能够顺利挂载,只是让智能体能力落地的前提;真正让大模型发挥作用的关键,在于它能否触达宿主环境之外的实时数据与服务。大模型本身无法直接访问实时互联网数据与第三方 REST 服务,必须依赖宿主环境提供的工具接口完成外部调用。在 DeepSeek Harness 架构中,所有模型可见的工具均通过 ctx.tools 容器统一注册与管理。本课时将基于这一机制,指导你通过 @deepseek-ai/dsh-tools 的 defineTool 函数开发一个外部 API 聚合工具插件,完成入参强校验、网络请求处理与数据封装返回。

核心概念

DeepSeek 工具定义机制与 JSON Schema 约束

设想你去一家快餐店点餐,菜单上明确列出每个套餐的名称、必选口味与配菜选项。你只需按格式报出套餐和选项,后厨便能精准出餐。如果菜单格式模糊,后厨就可能做错食物。

技术上,DeepSeek 工具定义机制(Function Calling Schema)是智能体与宿主工具之间的结构化契约。开发者使用 defineTool 声明工具的 name(名称)、description(自然语言功能描述)、parameters(基于 JSON Schema 规范的入参类型与必填约束)以及 output(返回值格式化与呈现规则)。DeepSeek 模型在理解用户意图后,生成符合该 Schema 约束的结构化 JSON 参数;Harness 自动执行参数校验并触发底层 execute 函数,将结果结构化注入模型上下文。

实现并挂载外部 API 聚合查询工具

  1. 1

    在插件工作区创建 src/weather-tool.ts,定义具备网络请求与超时容错逻辑的工具插件。

    typescript
    import type { Context } from '@deepseek-ai/cordis'
    import { defineTool } from '@deepseek-ai/dsh-tools'
    
    export const name = 'weather-aggregate-tool'
    export const inject = ['tools']
    
    export function apply(ctx: Context) {
      ctx.tools.register(defineTool({
        name: 'fetch_weather',
        description: 'Query real-time weather information and forecast for a specific city.',
        parameters: {
          city: {
            type: 'string',
            required: true,
            description: 'The city name to query weather for, e.g. Beijing, Tokyo, New York'
          },
          unit: {
            type: 'string',
            required: false,
            description: 'Temperature unit: celsius or fahrenheit. Default is celsius.'
          }
        },
        output: {
          schema: {
            type: 'object',
            properties: {
              city: { type: 'string' },
              temperature: { type: 'number' },
              condition: { type: 'string' },
              humidity: { type: 'number' }
            }
          },
          render: (_args, value) => [
            {
              type: 'text',
              text: `Weather in ${value.city}: ${value.temperature}° (${value.condition}), Humidity: ${value.humidity}%`
            }
          ]
        },
        async execute(args) {
          const unit = args.unit || 'celsius'
          const controller = new AbortController()
          const timeoutId = setTimeout(() => controller.abort(), 5000)
    
          try {
            // 模拟调用聚合 REST API 接口
            const simulatedData = {
              city: args.city,
              temperature: unit === 'fahrenheit' ? 77 : 25,
              condition: 'Sunny',
              humidity: 45
            }
            return simulatedData
          } finally {
            clearTimeout(timeoutId)
          }
        }
      }))
    }
  2. 2

    在配置文件 cordis.yml 中挂载该工具插件。

    yaml
    - name: './src/weather-tool.ts'
    
  3. 3

    启动本地 Web 运行时或 CLI 运行时加载插件配置。

    bash
    pnpm dsh web --patch ./cordis.yml

你应该看到

终端成功加载插件,并在 Web UI 或 CLI 中向模型发送查询指令后,模型能够准确识别意图并触发 fetch_weather 工具调用。

遇到问题:启动或运行时报错:Cannot read properties of undefined (reading 'register') 或工具未出现在模型上下文。

可能原因

  • 插件未声明 export const inject = ['tools'],导致 ctx.tools 在 apply 执行时尚未被服务容器实例化
  • 工具声明中的 parameters 字段属性结构缺失或不符合 JSON Schema 规范,导致模型推理时无法生成有效参数描述

排查步骤

  1. 检查插件源文件,确保导出 inject = ['tools'] 以声明对工具注册服务的强依赖
  2. 核对 defineTool 中的 parameters 与 output.schema,确保每个字段明确指定了 type 与必选标记 required
  3. 在终端启动日志中确认插件 Fiber 状态达到 ACTIVE 且无未满足的依赖挂起
自检:在 defineTool 中,output.schema 与 output.render 的分工分别是什么?

output.schema 用于声明工具 execute 函数返回值的规范化数据类型(canonical value),便于类型检查和结构化流转;output.render 则负责将该返回值转换为供大模型直接理解与消费的上下文呈现格式(如 text 消息块)。