DeepSeek Harness 桌面端来了!带你拆解核心机制

DeepSeek Harness(简称 DSH)是 DeepSeek 开源的 AI Agent 框架,在 GitHub 上受到了广泛的关注。
DeepSeek Harness的核心机制,Everything is a Plugin,也就是把各种功能都做成插件,可以按需替换。我们也可以把自己的业务当成插件一样接入进来,比如查询订单、读取内部数据、调用企业服务等,这样就可以让 Agent 在对话中帮我们处理业务。
这篇文章我们先聊聊插件是怎么工作的,再用订单查询做一个例子,看看怎么把业务接入 DSH 里面。
官方最近也给出了桌面端的APP,可以直接下载安装使用。

一、DeepSeek Harness 的插件能力
DeepSeek Harness 用插件组织 Agent 的各项能力,模型接入、工具、技能、会话、沙箱、存储、任务调度和 UI 都由插件提供,连负责驱动任务执行的 Agent Loop 本身也是一个插件。开发者可以沿用官方给出的默认组合,也可以根据需要调整其中的实现。
支撑这套架构的是 Cordis。它不规定模型该怎样调用,也不规定 Agent 该怎样执行任务,而是提供插件生命周期、服务依赖、事件通信和副作用管理机制。具体的 Agent 能力由插件实现,Cordis 负责协调这些插件的组合与运行。

通过配置选择和替换能力:对于已有的兼容插件,开发者可以通过配置调整组合,无需修改 Harness 的主体代码。例如,保留会话和工具体系,换用另一个模型适配器,就可以测试不同模型的表现。自己开发替代插件时,也需要遵守相应的接口、事件和生命周期约定,才能与其他插件协作。
时间可组合性:插件卸载时,清理它注册的能力和资源。 插件运行后,可能注册服务、监听事件,也可能创建定时器或网络连接。Cordis 会在卸载时撤销纳入生命周期管理的注册,并执行相应的资源清理逻辑。插件作者需要把自行创建的资源也交给这套机制管理,才能避免插件移除后仍有监听器或后台任务残留。
空间可组合性:依赖变化时,自动调整相关插件的运行状态。 这里的“空间”指组件之间的依赖关系。插件声明自己需要哪些服务,Cordis 据此协调它的激活和停用:必需的服务尚未就绪时等待,服务就绪后激活,服务消失时停用并清理,服务恢复后重新加载。这为运行期间替换插件提供了基础,不过相关插件可能需要重新初始化,正在执行的任务也不一定能无缝继续。
这种插件化架构,也为自我进化 Agent提供了一种可能:Agent 可以围绕任务检查已有能力,编写所需的插件或配置,再通过插件管理工具将其安装到当前应用中。DeepSeek Harness 的“创造模式”正是这一理念的实验性落地,它试图让 Harness 的配置本身也成为 Agent 可以操作和改变的对象。
二、插件有哪些类型
了解插件如何组合之后,再来看它们分别承担什么工作。如果我们让Agent查订单,就需要工具插件;接入模型服务时,就需要模型适配插件;调整执行权限,可以使用策略插件。下面按主要职责介绍几类常见插件,同一个插件也可以承担多种职责。
| 类型 | 常见需求 | 主要职责 |
|---|---|---|
| 工具插件 | 查询订单、读取文件、执行命令 | 把操作提供给模型调用 |
| 模型适配插件 | 接入不同模型服务 | 对接模型请求、响应和流式输出 |
| 服务插件 | 文件访问、命令执行、会话存储 | 提供供其他插件调用的公共能力 |
| 策略插件 | 权限检查、超时控制、重试 | 在执行过程的相应位置施加规则 |
| 界面插件 | 增加面板、输入组件、结果展示 | 提供用户交互与展示能力 |
| 外部系统接入插件 | 接入 MCP 服务或自动化客户端 | 对接外部协议与 Harness 能力 |

其中,服务插件把公共功能提供给其他插件调用。例如,订单查询工具和每日统计任务都要读取订单,可以共用一个订单服务。工具插件负责把查询操作提供给模型,执行时再调用这个服务,简单的查询也可以直接写在工具插件里。
上面按插件承担的工作分类。开发时,还需要考虑插件运行在哪里,因此又有 Host 插件和 Client 插件 的区分。以 Web 版为例,终端启动的 Harness 程序是 Host,负责调用模型、执行订单查询;浏览器里的页面程序是 Client,负责接收输入、显示结果。订单工具运行在 Host,订单详情界面运行在 Client。
MCP 和 Skill 也通过插件接入。已有 MCP 服务器提供订单查询时,MCP 客户端插件可以把它接成模型可用的工具。Skill 则是给 Agent 阅读的任务说明,例如查询前需要哪些信息,由相应插件查找和加载;实际查询仍由工具完成。
三、全部由插件构成,Agent 怎么运行起来
DSH 的 Agent 有一套明确的运行流程,默认由 agent-loop 插件负责。 主循环也和其他能力一样,通过插件机制加载,需要时可以在配置中替换。无论采用哪种实现,都要知道 Agent 是怎么创建、任务需要按什么顺序来执行,以及什么时候结束任务。
DSH 已经提供了一套默认的插件组合,模型调用、工具执行、会话记录和主循环都有各自的实现。我们可以直接使用这套组合,再加上自己的业务插件,不需要从头组装一个 Agent。
Agent Loop 负责把一次任务组织起来。 模型插件负责和模型通信,工具插件提供可以执行的操作,会话插件负责保存对话与执行记录。Agent Loop 按照主流程调用这些能力,让一次模型请求、一次工具执行和后续回答形成完整的链路。
比如用户让助手查订单,一次常见的执行过程是:
用户提出查询请求
↓
Agent Loop 整理上下文、系统提示词和可用工具,交给模型
↓
模型选择调用订单查询工具
↓
工具执行查询,执行流程记录结果,再把结果交给模型
↓
模型根据查询结果回答;如果还需要调用工具,就继续执行
↓
没有待继续的工作时,结束本轮

我们如果想要开发一个订单插件,我们只需要提供查询能力,并遵守工具接口和资源清理的要求。什么时候把工具交给模型、怎样把查询结果传回去、是否还要继续下一步,这些由已有的主流程处理。
插件之间也有明确的协作要求。 每个插件要声明自己依赖哪些服务,Cordis 需要等这些依赖的服务就绪后才会激活它。启动时,DSH 会检查必需插件的激活结果。如果已启用的默认主循环等必需插件没能启动,应用会报错并清理已加载的资源,普通可选插件失败则会给出一些诊断信息,其他成功激活的插件可以继续运行。
所以,DSH 能作为 Agent 运行,靠的是默认组合提供所需能力、Agent Loop 组织执行,以及插件共同遵守的接口和生命周期约定。插件化让这些实现可以替换,但替换后仍要满足同样的协作要求。尤其是主循环,换成自己的实现后,任务执行、取消、会话恢复等工作也要一并完成,不能只写一个反复调用模型的循环就算完成。
四、插件的运行机制
知道 Agent 的主流程由谁负责之后,我们就可以具体看一个插件怎么加入其中了。从加载时注册能力,到运行时使用服务、通过事件参与任务,再到卸载时清理资源,下面按这个过程逐步介绍。

加载插件时,先注册它提供的能力
前面说到,Agent Loop 会把可用工具交给模型。要让订单查询工具出现在其中,插件就需要先把它注册到 DSH 中。
这一步可以写在插件导出的 apply 函数里。插件激活时,Cordis 会调用这个函数,并传入上下文 ctx。插件通过 ctx 使用 DSH 已有的服务,也把自己提供的能力注册进去。先看一个最简单的插件结构:
import type { Context } from ‘@deepseek-ai/cordis’
export const name = ‘my-plugin’
export function apply(ctx: Context) {
// 在这里注册插件提供的能力。
}
这段代码还没有注册具体工具,后面我们的订单插件会在 apply 中注册 lookup_order。工具先注册,什么时候执行由模型决定。 插件激活时会先把查询能力准备好,等模型选择调用这个工具时,才执行工具的业务函数。
服务:通过上下文使用已有能力
上面的 ctx 提供了访问服务的入口,每项服务都有自己的名字。例如,ctx.tools 管理工具,订单插件通过它注册查询工具,ctx.llm 提供模型调用能力,ctx.agents 提供 Agent 相关接口。
插件通过 inject 声明必需的服务:
export const inject = [‘tools’]
Cordis 会等待工具服务就绪,再激活这个插件。
当一项能力需要复用或替换底层实现时,还可以进一步区分服务定义、服务提供方和消费方。以命令执行为例,dsh-shell 定义接口,dsh-bash-local 提供本地执行实现,dsh-tool-bash 将能力暴露给模型。
消费方和提供方都依赖服务定义。只要实现满足相同约定,替换提供方就不需要同步改写消费方。对于本文这样简单的订单查询,一个插件已经足够,以后出现多个数据来源或多个调用者,再考虑提取服务。
事件:在执行过程的合适位置介入
假设我们已经有了一个删除订单的工具,现在要加一条规定,只允许在营业时间内删单。这个检查可以放在工具执行前,通过监听 tools/pre-execute 来完成,不需要改动删除订单的代码。
插件用 ctx.on() 注册监听器。模型选中工具后,DSH 会先运行这里的检查,再决定是否执行删除操作。
ctx.on(‘tools/pre-execute’, async (exec, next) => {
const hour = new Date().getHours()
if (exec.name === ‘delete_order’ && (hour < 9 || hour >= 18)) {
return { kind: ‘deny’, reason: ‘非营业时间,禁止删单。’ }
}
return next()
})
这段代码检查的是运行程序所在机器的时间。早上 9 点之前、下午 6 点及以后,删单请求会收到 deny,模型也会从工具结果中读到拒绝原因。如果在允许的时间内,或者调用的是其他工具,就执行 next(),继续后面的检查。
这里的 next() 不能漏掉。一个事件可能有多个插件监听,当前监听器调用它,后面的监听器才有机会处理。这种方式在 Cordis 中叫瀑布式事件。如果插件要拒绝调用,可以像上面一样直接返回决定,如果只是观察或附加信息,处理完就要调用 next()。否则,后续处理也会被中断。
除了执行前的检查,DSH 还提供了 tools/execute 和 tools/post-execute,它们也采用瀑布式调用。要给工具加上超时或重试处理,可以监听 tools/execute,要修改、补充工具返回的结果,则用 tools/post-execute。超时处理还需要工具配合,插件会传入带截止时间的取消信号,工具发起的网络请求或子进程必须响应这个信号,到了时间才会停下来。
如果我们只想记录查询结果,或者统计工具的使用情况,监听 tools/result 就够了。这个事件在最终结果确定后发出,属于广播式事件。监听器拿到的是冻结的结果快照,可以读取,不能修改,也不需要调用 next()。
后面的订单查询示例暂时没有这些额外要求,直接注册工具即可。以后要增加执行前的检查或查询后的统计,再加相应的监听器。
生命周期:注册的能力要能撤销
假如订单插件启动了一个定时器,每隔一分钟查询一次订单,那么卸载插件时,也需要清理这个定时器。否则,即使订单查询工具已经从可用工具列表中移除,定时器仍可能继续发起查询。
通过 ctx.tools.register() 注册的工具、通过 ctx.on() 注册的监听器,Cordis 会在插件卸载时自动移除。插件自己创建的定时器、网络连接,则需要自己写好清理方法,交给 ctx.effect() 管理。定时查询可以这样写。
ctx.effect(() => {
const timer = setInterval(pollOrders, 60000)
return () => clearInterval(timer)
})
ctx.effect() 会先执行传入的函数,创建定时器,并保存它返回的清理函数。之后无论是配置重载、停用插件,还是退出应用,只要卸载这个插件,Cordis 就会调用清理函数,停止这次定时查询。
写插件时还要考虑会话恢复。比如助手已经查过一次订单,恢复会话后,这次查询的参数和结果也需要能找回来。DSH 会在正常的工具执行过程中记录这些内容,本文的订单插件可以直接使用。如果插件另外向对话添加信息,或保存会影响后续回答的业务状态,就要把相应的记录和恢复逻辑一并做好,让模型读到过的信息都能从 Session 日志中重建。
五、实践:开发一个订单查询插件
现在把前面的概念落实到代码,准备一个订单查询的插件,我们要做的事情是,接收订单号,查询三条模拟记录,返回商品、订单状态和物流单号。
准备开发环境
我们当前使用的是 0.1.7-rc.1 这个版本的源码,Deepseek Harness 的源码仍在迭代,使用其他版本时,需要对照该版本的开发要求。
该项目示例采用 Node.js 24,以及项目清单指定的 pnpm 11.7.0。检查当前软件的版本,版本过低则需要升级:
node –version
pnpm –version
确认版本符合要求后,先在终端进入 DeepSeek Harness 源码仓库的根目录,也就是包含 package.json、pnpm-workspace.yaml 和 packages 文件夹的那一层。将下面的路径换成你保存源码的实际位置:
cd “/你保存源码的位置/deepseek-harness”
然后安装依赖并构建项目:
pnpm install –frozen-lockfile
pnpm run build
等两条命令成功结束,再继续插件部分。
接下来,继续在仓库根目录执行以下命令,创建插件目录,并设置本次练习的数据目录:
mkdir -p scratch-order-plugin/src
export DSH_HOME=”$PWD/tmp/order-tutorial-home”
执行后,插件代码目录是仓库下的 scratch-order-plugin/src。后面的命令仍在仓库根目录执行,不需要进入这个新目录。
DSH_HOME 是告诉 DSH 把运行数据放在哪里的环境变量。上面的 $PWD 表示执行命令时所在的目录,因此这里指定的数据目录是仓库下的 tmp/order-tutorial-home。从这个终端启动 DSH 后,本次练习使用的应用配置、会话记录等数据会存放在那里,与日常使用的数据分开。插件代码仍放在 scratch-order-plugin 中。
这条 export 命令只对当前终端及从它启动的程序生效,关闭终端后,目录里的文件仍会保留。如果换了一个终端继续练习,需要重新进入同一个仓库,再设置一次变量,然后执行后面的启动命令:
cd “/你保存源码的位置/deepseek-harness”
export DSH_HOME=”$PWD/tmp/order-tutorial-home”
示例最终包含三个文件:
scratch-order-plugin/
├── src/
│ ├── orders.ts
│ └── index.ts
└── cordis.patch.yml
准备模拟订单
创建 scratch-order-plugin/src/orders.ts:
/** Tutorial-only orders. All products and tracking numbers are fictional. */
/** Fields returned by the tutorial’s in-memory order lookup. */
exportinterface Order {
orderId: string
product: string
status: ‘待发货’ | ‘已发货’ | ‘已取消’
trackingNumber: string | null
}
/** Fixed sample records; restarting the plugin restores these values. */
exportconst orders: readonly Order[] = [
{
orderId: ‘DS1001’,
product: ‘机械键盘’,
status: ‘已发货’,
trackingNumber: ‘DEMO-SF1001’,
},
{
orderId: ‘DS1002’,
product: ‘无线鼠标’,
status: ‘待发货’,
trackingNumber: null,
},
{
orderId: ‘DS1003’,
product: ‘显示器支架’,
status: ‘已取消’,
trackingNumber: null,
},
]
trackingNumber 为 null 表示没有物流单号。这个结果与“查询失败”含义不同,应当保留。三条记录分别覆盖已发货、待发货和已取消,后面可以逐一核对。
如果订单数据来自外部 API
上面的三条模拟订单,也可以由一个接口以 JSON 数组的形式返回。假设接口地址是 https://example.com/api/orders,获取数据的写法就像这样:
const response = await fetch(‘https://example.com/api/orders’)
const orders: Order[] = await response.json()
const order = orders.find(item => item.orderId === ‘DS1001’) ?? null
拿到的 orders 与前面的模拟数组一样,仍然可以按订单号查询。这里的地址只是示意,实际使用时换成自己的接口;后面的完整示例继续使用本地模拟数据。
实现完整的查询插件
创建 scratch-order-plugin/src/index.ts,写入以下代码:
/** Read-only order lookup using fictional tutorial data. */
importtype { Context } from’@deepseek-ai/cordis’
import Schema from’@deepseek-ai/schemastery’
import { defineTool } from’@deepseek-ai/dsh-tools’
import { orders } from’./orders.ts’
/** Cordis diagnostic name. */
exportconst name = ‘order-demo’
/** Required services before activation. */
exportconst inject = [‘tools’]
/** Deployment settings for the sample shop. */
exportinterface Config {
storeName: string
}
/** Validate settings and provide the sample shop’s default name. */
exportconst Config: Schema<Config> = Schema.object({
storeName: Schema.string().default(‘演示商店’),
})
/**
* Register the order lookup for this plugin’s lifetime.
* @param ctx – Plugin context with the tools service.
* @param config – Validated shop settings.
*/
exportfunction apply(ctx: Context, config: Config) {
ctx.tools.register(defineTool({
name: ‘lookup_order’,
description: ‘按订单号查询演示商店的模拟订单。只读,不修改订单;查不到时请说明未找到。’,
parameters: {
orderId: {
type: ‘string’,
required: true,
description: ‘订单号,例如 DS1001。’,
},
},
output: {
schema: {
type: ‘object’,
additionalProperties: false,
properties: {
storeName: { type: ‘string’, required: true },
orderId: { type: ‘string’, required: true },
order: {
required: true,
oneOf: [
{ type: ‘null’ },
{
type: ‘object’,
additionalProperties: false,
properties: {
orderId: { type: ‘string’, required: true },
product: { type: ‘string’, required: true },
status: {
type: ‘string’,
enum: [‘待发货’, ‘已发货’, ‘已取消’],
required: true,
},
trackingNumber: {
required: true,
oneOf: [{ type: ‘string’ }, { type: ‘null’ }],
},
},
},
],
},
},
},
render: (_args, value) => [{
type: ‘text’,
text: value.order === null
? `【模拟数据】${value.storeName}:未找到订单 ${value.orderId},请核对订单号。`
: [
`【模拟数据】${value.storeName}`,
`订单号:${value.order.orderId}`,
`商品:${value.order.product}`,
`状态:${value.order.status}`,
`物流单号:${value.order.trackingNumber ?? ‘暂无’}`,
].join(‘\n’),
}],
},
async execute(args) {
const orderId = args.orderId.trim().toUpperCase()
if (orderId.length === 0) {
thrownewError(‘订单号不能为空,请提供 DS1001 这样的订单号。’)
}
return {
storeName: config.storeName,
orderId,
order: orders.find(order => order.orderId === orderId) ?? null,
}
},
}))
console.log(`[order-demo] 已注册 lookup_order,店铺:${config.storeName}`)
}
代码中占篇幅最多的是返回字段声明。实际查询集中在 execute:整理订单号,从数组查找,再返回订单对象或 null。
前面讲过的几个机制,在这里都有了具体对应:apply 注册工具,inject 声明对工具服务的依赖,Config 校验部署配置。还需要仔细区分两组内容。
部署配置与调用参数
storeName 属于 Config,由部署者设置;orderId 属于 parameters,由模型在每次调用时传入。
例如,“演示商店”是插件配置,“DS1001”是这次查询的输入。以后接入真实服务时,服务地址也适合放在部署配置中,查询对象则继续作为调用参数。
defineTool 根据参数声明推导类型并校验输入。这里要求 orderId 是必填字符串,但字符串是否为空还需要业务代码判断,所以 execute 中保留了非空检查。
程序结果与模型内容
execute 返回结构化数据,output.schema 声明它应该包含的字段。order 可以是订单对象,也可以是 null;代码中的 oneOf 表示结果必须匹配所列分支中的一个。
output.render 把同一次执行的结果转换为模型可读的文字,不再查询一次订单。程序可以读取结构化字段,模型可以阅读包含“状态”“物流单号”的说明。
这里让错误含义更加明确:DS9999 这个订单不存在,是一次正常完成工具调用、结果为“未找到”的查询;订单号为空,则是不满足输入要求。接入真实 API 后,网络不可达或认证失败这是另外一类的错误问题。
加载插件,并让模型调用它
编写加载配置
在仓库根目录的 scratch-order-plugin 文件夹中,新建 cordis.patch.yml,写入以下完整内容:
– insert:
– id: order-demo
name: “/你的仓库绝对路径/scratch-order-plugin/src/index.ts”
config:
storeName: ‘演示商店’
将 name 中的 /你的仓库绝对路径 替换为本机 DeepSeek Harness 仓库的实际位置,保留后面的 /scratch-order-plugin/src/index.ts。例如,仓库位于 /home/lin/deepseek-harness,这一行就写成 name: “/home/lin/deepseek-harness/scratch-order-plugin/src/index.ts”。这里需要使用绝对路径,不能直接保留示例占位文字。
id 是配置条目标识,name 是插件模块路径,上面源码中定义的lookup_order 才是模型调用的工具名。保存时保留 YAML 的缩进,使用空格,不要使用 Tab。如果以后移动了仓库,也要更新 name 中的路径。
启动测试应用
在设置过 DSH_HOME 的终端执行,这里我们配置的源码的根目录:
pnpm dsh web –patch ./scratch-order-plugin/cordis.patch.yml –port 3088 –no-open
示例使用 3088 端口,并关闭自动打开浏览器。插件完成注册时,会输出:
[order-demo] 已注册 lookup_order,店铺:演示商店

这条信息表示 apply 已运行并完成工具注册,还需要继续验证模型能否调用。
保持终端运行,在浏览器中打开终端打印的完整访问地址。地址以 http://127.0.0.1:3088/ 开头;如果带有 token 参数,访问时需要保留。
如果页面提示尚未配置模型,在“设置 → 模型”中设置可用凭据。独立测试目录可能需要重新配置。在插件里面搜索order就可以看到我们刚刚配置的插件了

在界面中打开一个工作目录并新建会话,选择已配置的模型。先发送一条普通消息,确认模型能够回复,再测试插件,便于区分模型连接问题与插件问题。


让插件随 DSH 启动自动加载
–patch 只对本次启动生效,DSH 不会记住这个命令行参数。如果希望以后自动加载,可以把刚才的插件配置保存到 Web Profile 的配置文件中:
$DSH_HOME/profiles/web/cordis.patch.yml

按照前面设置的 DSH_HOME,它就在仓库的 tmp/order-tutorial-home/profiles/web/cordis.patch.yml。首次启动 Web 后,这个文件会自动创建。停止服务,打开它,将 scratch-order-plugin/cordis.patch.yml 中的 – insert: 整段内容加入文件,如下图所示

保存后,每次启动只需:
pnpm dsh web –port 3088 –no-open
DSH 会自动读取该 Profile 中的订单插件配置,不必再传 –patch。–port 3088 和 –no-open 只是指定端口、关闭自动打开浏览器,如果接受默认行为时,也可以直接执行 pnpm dsh web。
后续启动要使用同一个 DSH_HOME。如果没有设置它,默认读取的是 ~/.dsh/profiles/web/cordis.patch.yml。自动加载后,修改店铺名称等配置也应编辑 Profile 中的这份文件。
八、在创造模式里让 DSH 帮你开发插件
前面的订单插件,我们自己准备文件、编写代码,再通过配置加载。这些工作也可以交给 DeepSeek Harness 的创造模式。它带有插件开发指引、运行时 API 检查和插件管理工具,可以根据需求编写插件,并尝试安装到当前应用中。
这次我们不使用本地运行的web端,而是使用官方的桌面端来开发,这里继续使用同样的三条模拟订单,目标也一样,让助手收到订单号后调用查询工具,再根据结果回答。这样我们就能对照前面的源码示例,看清楚 Agent 代替我们完成了哪些工作,以及哪些地方仍然需要人工检查。
选好模式和工作目录。 打开桌面端,选择工作空间,打开一个新的会话,在聊天窗口左上角选择创造模式
把需求一次说清楚。 在新的会话中发送下面这段提示词。桌面端的案例使用 lookup_demo_order 作为工具名称,方便与前面的源码示例区分。
请帮我开发并安装一个 DSH 订单查询插件,让我能在当前桌面 App 的对话里查询模拟订单。
数据直接保存在本地插件中,不连接外部 API、不使用数据库。准备以下三条虚构数据:
– DS1001:机械键盘,已发货,物流单号 DEMO-SF1001。
– DS1002:无线鼠标,待发货,暂无物流单号。
– DS1003:显示器支架,已取消,暂无物流单号。
提供一个名为 lookup_demo_order 的工具,接收订单号,返回商品、状态和物流单号,并标明是模拟数据。忽略订单号两端的空格和字母大小写;查不到就说明未找到,不编造发货或到货时间。
把这段提示词发送给 DeepSeek Harness,接下来就可以观察它的开发过程。

Agent 会根据当前版本选择实现方式,所以生成的文件可能与源码示例不完全相同。我们主要看它有没有完成工具定义、订单查询和插件安装。如果过程中弹出授权,先看清要执行的操作,再决定是否允许。
这次开发并没有一次成功。 中间出现报错时,我最初以为只是生成的代码有问题,让 Agent 根据错误提示继续修改就行。但后面发现,新开会话、换工作区,仍然会遇到同样的错误,连正常对话都受到了影响。

后来检查生成的代码,发现问题出在工具注册时的 parameters,Agent 使用了不符合 DSH 要求的参数声明格式。前面的源码示例中,parameters 直接按 orderId 这样的参数名组织,每个参数再声明类型、说明和是否必填。让模型写出一段查询数组的代码并不难,但把这段代码接到 DSH 里,还需要它正确使用当前版本的工具接口。
创造模式能省下手动编写和安装插件的工作,但使用者仍然需要能看懂报错、找到生成的文件,并在必要时修改代码。尤其是刚开始尝试时,适合先用这里的模拟订单练习。如果完全没有排查代码和配置的经验,目前直接用它改动日常工作的应用,遇到这种问题会比较难处理。
修正参数声明后,应用恢复了正常,在插件列表中也可以看到新开发的订单查询工具。

看到插件之后,还要实际查一次订单。 在聊天窗口发送下面这段话,检查执行记录里是否调用了 lookup_demo_order,再核对返回内容。
查询 DS1002,告诉我商品、订单状态和物流单号

按照前面准备的数据,DS1002 对应无线鼠标,状态是待发货,暂无物流单号。这里要把工具调用记录和最终回答放在一起看,因为开发提示词里已经给出了全部模拟订单,模型只靠聊天上下文也能复述这些信息。看到回答正确,还不足以确认插件已经工作,这里可以查看一下轨迹的运行记录

从运行轨迹可以看出,工具插件正常运行。通过查询指定编号的订单或者数据。
这次尝试里,DSH 根据一段需求,给自己增加了一个订单查询工具,修正代码后也确实调用成功了。以后需要查别的业务数据,也可以按这个方式,让它开发相应的插件,接入数据,再在对话里调用。
不过,中间那个参数格式错误也给我留下了更深的印象。创造模式已经能帮我们做不少开发工作,但出错之后,仍然可能需要人来接手。这次订单查询这个插件能用起来,靠的也是最后把参数声明改对了。想尝试这种开发方式,除了想好要加什么功能,也得考虑出了问题后,自己能不能把应用恢复到正常状态。
作者:叶小钗
来源:叶小钗
扫一扫 微信咨询
商务合作 联系我们
微信扫一扫 