← 返回列表
未验证
按工具限制调用次数与结果字节,超限即拒绝
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/1 · 已提供中文文档
DeepSeek Harness 插件:按工具调用与结果字节上限
综合分
28.4
GitHub 分
28.4
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add jwilson411/dsh-tool-quota该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/16(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-tools@deepseek-ai/cordis用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
dsh-tool-quota
一个 [DeepSeek Harness][dsh] 函数插件:按工具限制调用次数和结果字节数,并大声强制执行。
一个 agent 被赋予了一个搜索工具。任何单次调用本身都没有问题,所以没有什么能阻止它调用八十次,或者拉回一个四兆字节的页面并把整个内容放进对话里。
这个插件对两者都加以限制,按工具、按会话:
- id: tool-quota
config:
arxiv_search:
maxCalls: 8
maxResultBytes: 32000
"":
maxCalls: 40
maxResultBytes: 64000
对 arxiv_search 的第九次调用根本不会运行:
ToolQuotaCallsError: tool quota: "arxiv_search" is capped at 8 calls per session
and has used 8; call 9 was denied before it ran. Use what the earlier calls
returned, call a different tool, or stop.
而超过字节上限的结果会被丢弃,而不是继续传递:
ToolQuotaBytesError: tool quota: "arxiv_search" returned 41231 bytes, over its
32000-byte cap; the result was discarded rather than truncated. Ask for less — a
narrower query, fewer items, or a smaller range.
两者都带有一个 code —— TOOL_QUOTA_CALLS、TOOL_QUOTA_BYTES —— 这样调用方无需解析文字就能分支处理。
它不是什么
不是花费预算。 这个包里任何地方都没有美元,没有价格表,也没有账单。一次调用就是一次调用,无论它花了十分之一美分还是一分钱都没花。
不是 token 计量。 没有分词器,没有上下文窗口计算,没有模型调用。32000 字节就是 32000 字节,无论分词器会怎么看待它们。金钱和 token 是另一个包的职责。
不是静默截断。 这里没有任何东西会缩短结果。一个截断式上限返回的东西看起来像是一个完整答案,只是尾部悄悄缺失了,而下游的读者——通常是一个模型——无从分辨。超配额的结果会被丢弃,调用会失败。
不是速率限制器。 这个模型里根本没有时间:没有每分钟窗口,没有突发,没有休眠和重试。配额是一个会话的总量,一旦用完就一直用完,直到会话结束或插件重新加载。
不是权限系统。 配额限制的是一个工具被使用的量,而不是它是否本应可达。
安装
dsh plugin --profile default add github:jwilson411/dsh-tool-quota
安装程序会从包清单中读取 dsh.bundle.patch,并将此包追加到该 profile 的有序 bundle 列表。它的 cordis.patch.yml 带有一行插入记录,id: tool-quota,配置为空——上限属于部署,而不属于包,所以在 profile 说明要限制什么之前,安装这个插件不会限制任何东西。
将 tools 包固定为 0.1.1-rc.2;这是本插件开发和测试所针对的候选发布版本。
配置
该行的整个 config 块就是规则映射。它的键是工具名称,外加保留键 ;每个值是一条最多包含两个字段的规则。
| 键 | 含义 |
| --- | --- |
| maxCalls | 一个会话可以调用该工具的次数。包含被拒绝的调用:设为 8 时,八次调用会执行,第九次会抛出异常。省略表示没有调用上限;0 表示直接拒绝该工具。 |
| maxResultBytes | 单个结果可以携带的 UTF-8 字节数。超过上限时,结果会被丢弃。省略表示没有字节上限。 |
两者都必须是至少为 0 的整数。数字字符串("8")会被接受,
因为 YAML 加引号很容易出意外。其他任何东西——浮点数、负数、
单词——都会在插件应用时被拒绝,而不是在第一次调用时。
规则中的未知键会被拒绝,而不是被忽略。一个拼错的
maxCall: 8 悄悄意味着“完全没有限制”,正是这个
插件存在要防止的失败。
哪条规则管辖某个工具
具名规则会作为一个整体对象优先于 。不存在字段合并。
arxiv_search:
maxCalls: 100 # …并且没有字节上限,即使 "" 设置了上限
"":
maxCalls: 40
maxResultBytes: 64000
- 映射中具名的工具 → 使用它自己的规则,完全按所写内容执行。
- 未具名的工具 → 使用 规则。
- 未具名且没有 规则 → 完全没有上限;该调用甚至不会被计数。
- {} 作为具名规则 → 匹配,但不受任何限制。这就是让某个工具
豁免于 规则的方式。
合并会让具名规则无法放宽,也会让读者无法在不把两个地方
在脑中组合起来的情况下说出某个工具受什么管辖。
按 id 定向的补丁会替换该行的整个 config,而不是合并进去,
因此覆盖必须重新声明它想保留的每条规则。
计数
计数是按会话和按工具名称进行的。
- 共享一个进程的两个 agent 不会花掉彼此的额度。会话是
调用方传入的显式 sessionId,否则是执行输入中调用 agent 的 id,否则是 default。
- 在 下,每个工具有自己的额度,而不是共享一个池:
maxCalls: 40 意味着对每个未设上限的工具各四十次调用,而不是总共四十次。
- 被拒绝的调用不会被记录,因此 used 永远不会超过上限。
- 计数与插件同寿。重新加载会让每个会话以完整额度开始,
这是正确的默认行为:一个跨会话记住计数的跟踪器会因为上一次运行
花掉的东西而拒绝一次全新运行的第一次调用。
字节
字符串结果按其自身测量,使用 Buffer.byteLength(result, 'utf8')。
其他任何东西都按其紧凑 JSON 测量,因为那才是实际
到达对话中的形式。
JSON 无法表示的值——BigInt、循环引用——会被视为超出配额,
并以 reason: 'UNMEASURABLE' 抛出。无法确定的尺寸不是
能放得下的尺寸,在这里猜测就意味着把一个未测量的负载传下去。
字节检查在工具主体之后运行。调用已经发生,
也已经付出代价;上限保护的是对话,而不是工具。
超大的值永远不会附加到错误上——重点就在于它
不会再往前走。
错误
error.code // 'TOOL_QUOTA_CALLS'
error.toolName // 'arxiv_search'
error.sessionId // 配额耗尽的会话
error.count // 9 —— 本次调用的序号,包含被拒绝的这一次
error.used // 8 —— 实际执行的调用次数
error.maxCalls // 8
error.plugin // 'dsh-tool-quota'
error.code // 'TOOL_QUOTA_BYTES'
error.toolName // 'arxiv_search'
error.sessionId // 该调用所属的会话
error.byteLength // 41231,当无法测量该值时则为 null
error.maxResultBytes // 32000
error.reason // 'OVER_LIMIT' | 'UNMEASURABLE'
error.plugin // 'dsh-tool-quota'
两者都是抛出,而非返回。一个调用方只要忽略返回值就能越过的上限,算不上上限。这两个错误都不携带工具的参数或结果正文:那些东西最可能包含路径、查询或凭据,而错误消息最可能被记录到日志里。
状态工具
注册了一个面向模型的工具,tool_quota_status。它不接受任何参数,并针对调用它的会话,报告配置中列出的每个工具,以及该会话已经调用过的每个被 覆盖的工具:
{
"plugin": "dsh-tool-quota",
"sessionId": "agent-1",
"tools": [
{ "name": "arxiv_search", "rule": "tool", "used": 3, "maxCalls": 8,
"remaining": 5, "maxResultBytes": 32000 }
],
"star": { "maxCalls": 40, "maxResultBytes": 64000 }
}
remaining 为 max(0, maxCalls - used),对于规则未设置 maxCalls 的工具则为 null。 限制分别适用于每个未设上限的工具,而不是将它们合在一起计算。不涉及金钱、token 或时间。
调用它不会消耗任何配额——一个可能耗尽配额的状态工具,会是一种奇怪的消耗方式——而一个能询问自己还剩多少额度的 agent,就不必靠撞上上限来发现它。
它如何挂载
apply 在两处对注入的工具注册表打补丁,并返回 QuotaTracker,以便想要检查或重置它的宿主使用。
- ctx.tools.register 被打补丁,因此在此 fiber 生命周期内注册的每个定义都带有一个计量的 execute。这是强制路径,也是此插件假定存在的唯一路径。
- ctx.tools.execute 在运行环境暴露它时被打补丁,这能捕获 register 补丁无法捕获的情况:在此插件应用之前*注册的工具。
一次宿主调用同时经过这两个接缝时,只会被计数一次,以其调用 id 和工具名作为键。它也只被测量一次,由内层包装器完成,后者看到的是工具返回的规范值,而不是运行环境围绕它构建的任何封装。
配置在接触注册表之前就已校验,因此不可用的规则会直接失败,不会留下半安装状态。两个补丁都会在 dispose 时撤销,使注册表完全恢复为被发现时的样子。
直接使用该库
计数的那一半会单独导出,供拥有其调用点的宿主使用:
import { QuotaTracker, decorateTool, wrapExecute } from 'dsh-tool-quota/quota'
const tracker = new QuotaTracker({
rules: { arxiv_search: { maxCalls: 8, maxResultBytes: 32000 } },
})
// 包装一个 execute……
const metered = wrapExecute(execute, tracker, { tool: 'arxiv_search', sessionId: 's1' })
// ……或者复制整个定义,并对其 execute 进行计量。
const capped = decorateTool(definition, tracker)
tracker.status('s1') // 该会话还剩多少
decorateTool 返回一个副本;原始定义保持不变。同样导出的还有:
normalizeRules、normalizeLimit、ruleFor、resultByteLength、
sessionKey、ToolQuotaCallsError、ToolQuotaBytesError、
InvalidQuotaConfigError。
依赖
node:buffer 和 @deepseek-ai/dsh-tools —— 仅此而已。发布的源码
不打开任何套接字、不读取任何文件、不生成任何进程,这些都是测试套件
断言的结果,而非承诺。@deepseek-ai/cordis 和
@deepseek-ai/dsh-tools 是 peer 依赖,由测试框架提供。
Node >=22.14.0。
测试
npm install
npm test
无需网络、无需凭据、无需模型权重。CI 在 Node 22.x
和 24.x 上运行同一套测试。
许可证
MIT。版权所有 (c) 2026 jwilson411。
[dsh]: https://www.npmjs.com/package/@deepseek-ai/dsh-tools同作者(jwilson411)的其他插件
扫码进群