工具系统------Agent的手脚
本周目标:
- 工具是怎么定义的
- 工具调用是怎么执行的
- 怎么保证工具调用安全
怎么定义一个工具
java
// 一个工具需要告诉系统四件事:
// 1. 我叫什么名字?
// 2. 我能干什么?
// 3. 我需要什么参数?
// 4. 我怎么执行?
ctx.tools.register({
// 1. 名字
name: 'search',
// 2. 描述(LLM 看到这个来判断什么时候用它)
description: '搜索网页,返回相关结果',
// 3. 参数(LLM 需要提供什么)
parameters: {
query: {
type: 'string',
required: true,
description: '搜索关键词'
},
limit: {
type: 'number',
description: '返回结果数量'
}
},
// 4. 执行逻辑
async execute(args, exec) {
const results = await fetch(`https://search.example.com?q=${args.query}`)
return results.slice(0, args.limit ?? 10)
}
})
LLM 看到的工具定义:
java
{
"name": "search",
"description": "搜索网页,返回相关结果",
"parameters": {
"query": { "type": "string", "description": "搜索关键词" },
"limit": { "type": "number", "description": "返回结果数量" }
}
}
工具执行管道
dsh最复杂最核心的设计。用公司审批流程类比:
场景:员工(Agent)要调用一个工具(比如删除文件)
不能让员工直接删!需要经过层层检查:
员工发起请求
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 第1层:tools/pre-execute(审批大门) │
│ │
│ "这个调用允许吗?" │
│ → allow(允许)→ 继续 │
│ → deny(拒绝) → 停止,返回错误 │
│ → ask(询问) → 弹出确认框,等用户回答 │
│ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 第2层:ToolGuard(安全策略) │
│ │
│ "有没有硬性规则禁止这个调用?" │
│ → 返回 undefined(没意见)→ 继续 │
│ → 返回 "原因"(拒绝)→ 停止 │
│ │
│ 比如:"禁止删除系统文件" │
│ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 第3层:tools/execute(执行包装) │
│ │
│ 在执行外层加上: │
│ - 超时控制(超过5分钟自动停止) │
│ - 重试逻辑(失败后自动重试) │
│ - 指标收集(记录执行时间) │
│ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 第4层:Tool Body(真正执行) │
│ │
│ 实际运行工具的代码: │
│ const result = await search(args.query) │
│ return result │
│ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 第5层:tools/post-execute(结果处理) │
│ │
│ 执行完了,可以: │
│ - 接受结果(accept) │
│ - 替换结果(replace) │
│ - 阻止结果(block) │
│ - 附加上下文(add context) │
│ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 第6层:finalizeContent(最终处理) │
│ │
│ 工具定义自己的"最后一步": │
│ - 格式化输出 │
│ - 过滤敏感信息 │
│ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 第7层:tool/result(持久化到日志) │
│ │
│ 结果写入 Session Event Log │
│ → LLM 看到这个结果 │
│ │
└─────────────────────────────────────────────────────────────┘
上述流程有个大概印象就好,最主要理解的是下面三种拦截方式。
三种拦截策略

java
// 方式1:pre-execute 审批
ctx.on('tools/pre-execute', (execution, next) => {
if (execution.name === 'delete_file') {
// 弹出确认框
const allowed = await askUser('确定要删除吗?')
if (!allowed) return { kind: 'deny', reason: '用户取消' }
}
return next()
})
// 方式2:Guard 硬性策略
ctx.tools.guard((execution) => {
if (execution.name === 'delete_file') {
const path = execution.arguments.path
if (path.startsWith('/system/')) {
return '禁止删除系统文件' // 直接拒绝
}
}
return undefined // 不干涉
})
// 方式3:post-execute 结果处理
ctx.on('tools/post-execute', (execution, result, next) => {
if (execution.name === 'read_file') {
// 过滤敏感信息
return { ...result, value: filterSecret(result.value) }
}
return next()
})
取消机制(协作式)
java
场景:Agent 发起了一个耗时操作,用户想取消
传统做法(强制杀死):
直接 kill 进程 → 可能导致数据损坏
dsh 的做法(协作式取消):
发一个"请停止"信号 → 工具体自己决定怎么优雅停止
ctx.tools.register({
name: 'long_task',
async execute(args, exec) {
// exec.signal 是取消信号
for (let i = 0; i < 100; i++) {
// 每次循环检查是否被取消
if (exec.signal.aborted) {
console.log('收到取消信号,优雅退出')
break
}
await doSomething()
}
}
})
总结
一句话概括
工具是 Agent 的"手脚",dsh 通过多层执行管道实现工具的安全调用和灵活扩展。
核心知识点
- 工具定义
一个工具需要四要素:
- name:叫什么
- description:干什么(LLM 看这个决定什么时候用)
- parameters:需要什么参数
- execute:怎么执行
- 执行管道(7层)
pre-execute(审批)
→ guard(安全策略)
→ execute(超时/重试包装)
→ body(真正执行)
→ post-execute(结果处理)
→ finalizeContent(最终格式化)
→ result(持久化到日志)
- 三种拦截方式
方式 作用 特点
pre-execute 审批门 可 allow/deny/ask
ToolGuard 硬性策略 一旦拒绝不能翻案
post-execute 结果处理 可替换/阻止结果
- 关键设计
注册即效果 → 插件卸载时工具自动移除
协作式取消 → 发信号让工具自己优雅停止
Model-visible means logged → 工具调用和结果都写入 Session Log
面试怎么答
"dsh 的工具系统不是简单的直接调用,而是一个多层管道。每次工具调用要经过审批门、安全策略、超时包装、真正执行、结果处理等七个环节。任何环节都可以拦截或修改,这让安全策略和工具实现完全解耦。取消机制是协作式的------发信号让工具自己优雅停止,而不是强制杀死。"
一页速记
工具注册:ctx.tools.register(definition)
执行管道:pre-execute → guard → execute → body → post → finalize → result
拦截方式:pre-execute(审批) / Guard(硬性) / post-execute(结果)
取消机制:AbortSignal 协作式取消
持久化:tool/call + tool/result 写入 Session Log