Skip to content

工具展示是从"模型请求调用某个工具"到"结果回到模型历史"的整条路径:schema 展示、分发、策略、受守护执行与事件记录。它横跨作用域工具注册表(packages/core/tools/src/index.ts,暴露为 ctx.tools)、循环的调度器(packages/core/agent-loop/src/tool-calls.ts)以及智能体平面的展示选择器(packages/core/agent-tool-presentation/src/index.ts)。

负责
@deepseek-ai/dsh-toolsctx.tools——作用域工具注册表、defineTool、守卫、presentAsrestrict、执行流水线
@deepseek-ai/dsh-agent-loop计划/分发一个 step 工具调用并保持模型顺序的调度器
@deepseek-ai/dsh-agent-tool-presentation"模型看到的工具是哪种形态"的选择器(native/code/both
@deepseek-ai/dsh-llmToolSchemaToolCallBlockcreateToolResultMessage

对模型可见的 schema

每个已注册工具都是一个 ToolDefinition,其对模型可见的表面是声明于 packages/llm/llm/src/types.tsToolSchema

ts
export interface ToolSchema {
  name: string
  description: string
  /** JSON Schema object for the arguments. */
  parameters: Record<string, unknown>
}

操作上交,ToolDefinition 加了 execute(args, exec) 主函数以及可选的最终内容与 UI 回调——但注册表的 schemas(scope) 方法只向模型投影 namedescriptionparameters(白名单),并通过 structuredClone 解绑参数对象,因此不会有任何运行时状态泄漏到提示。工具通过 defineTool DSL(packages/core/tools/src/schema.ts)编写,它把类型化 ValueSchemaSpec/ParameterSchemaSpec 编译成 JSON Schema 加生成的类型。

展示作用域工具与全局工具

工具 schema 经 systemPrompt.tools(provider) 到达提示。工具注册表只贡献该作用域可见的 schema——经受作用域限制过滤后的全局工具,加上作用域本地注册——并在遮蔽之后(作用域同名工具会为该作用域隐藏全局孪生)。这些 schema 如何渲染由展示模式决定:

模式模型看到什么
native每个可见的 ToolSchema
code仅保留的 run_code 传输 + 生成的 SDK(Code Mode)
bothrun_code 传输与本地 schema 都要

ctx.tools.presentAs(mode)挂载作用域(一行对应一个智能体预设的常驻挂载,而非一个会话)声明该模式。agent-tool-presentation 插件读取 mode 配置,然后直接 presentAs('native'),或等待 codeRuntime 服务后再声明代码模式。

执行流水线

注册表的流水线先运行 tools/pre-execute waterfall(钩子、权限、沙箱),再运行注册的单调守卫,然后是 tools/executetools/post-execute waterfall,最后是定义所有的 finalizeContenttools/result 通知。流程的 ASCII 图:

助手消息 → tool-call 块
        │  为每个块计划一个 ToolExecutionInput

[记录 tool/call]  [UI  presentCall(args)]


tools/pre-execute waterfall   (钩子、权限、沙箱)→ 允许 / 拒绝 / 询问
        ▼ 允许
注册的单调守卫(拒绝或弃权;身份受保护)
        ▼ 允许
tools/execute waterfall      (超时、重试、指标包裹主体)

注册工具的 execute() 主体
        │  工具自有事件:todo/write、fs/observed、hook/invoked、…

tools/post-execute waterfall (接受 / 阻止 / 替换 / 加上下文)

外层归一化(流水线/结果快照抛错 → isError)

ToolDefinition.finalizeContent (最后一个仅内容不变量)

tools/result(同步、冻结的权威结果)

[记录 tool/result → 引用其 tool/call 的 seq]  [UI  presentResult(args, result)]

工具批次落定 → additionalContexts FIFO → 注入 next-step user/message

ctx.approval 在单调守卫之前解析询问类调用;不能被重排序的持有者策略保持为注册守卫。围绕分发的问题(超时)包装 tools/execute。注册表无损地快照候选结果,并在可见定义的快照式 finalizeContent 回调强制执行其同步的仅内容不变量之前归一化快照失败,因此 tools/result 总能观察到一份不可变的、无损 JSON 的结果。

循环的调度器与事件映射

executeToolCalls(位于 packages/core/agent-loop/src/tool-calls.ts)按活跃并发模式调度一个助手 step 的工具调用:独占调用形成屏障,并行调用使用一个以 ctx.agentLoop.config.maxParallelToolCalls 为上限的有界滚动池。尽管分发可以重叠,事件仍按模型顺序提交。到会话日志的持久映射:

ts
function appendToolCall(session, turn, step, block): number {
  const event = session.append('tool/call', { turn, step, callId: block.id, name: block.name, arguments: block.arguments })
  return event.seq
}
ts
function appendToolResult(session, turn, step, block, result, callSeq): void {
  const message = createToolResultMessage({ callId: block.id, content: result.content, isError: result.isError })
  session.append('tool/result', {
    turn, step, message,
    ...result.error?.info ? { error: result.error.info } : {},
    ...result.meta !== undefined ? { meta: result.meta } : {},
  }, { surfaceOp: 'append', sourceEventSeqs: [callSeq] })
}

模型 tool-call 块里的 callIdtool/call 与它的 tool/result 配对;结果在 sourceEventSeqs 中引用所属调用的 seq,保留了回放所需的相邻关系。结果可以携带 concludesTurn(让 turn 在该 step 处结束)与 additionalContexts(调度器的 acceptContext 把它们拼进 next-step inbox 作为 user/message)。取消时,跳过的模型调用会收到一条合成错误结果(TOOL_ABORTED_BEFORE_DISPATCH),从而保证回放仍然有效。

调度器表面与结果契约

把执行分成两个重叠阶段(有序策略,然后并发分发)的是工具注册表内部、由循环通过符号 TOOL_RUNTIME_SCHEDULER 访问的调度器。它的三个操作把各阶段串联起来:

ts
interface ToolRuntimeScheduler {
  /** Materialize input, run the ordered pre-execute/guard gate, and decide what stage follows. */
  prepare(exec: ToolExecutionInput): Promise<ScheduledToolPreparation>
  /** Run only the around-dispatch/body stage. */
  dispatch(exec: ToolRunContext): Promise<ScheduledToolDispatch>
  /** Run post-execute and definition-owned content finalization, then materialize and notify. */
  finalize(exec: ToolRunContext, result: ToolExecutionResult): Promise<ToolExecutionResult>
  /** Run definition-owned content finalization, then materialize and notify without post-execute. */
  finish(exec: ToolRunContext, result: ToolExecutionResult): ToolExecutionResult
}

prepare 返回一个阶段决定:dispatch 还需要主体,post-result 仍要走 tools/post-execute,而 final-result 已匹配失败语义并跳过它。tool-calls.ts 里的提交在结果是 post-result 时调用 finalize、在 final-result 时调用 finish——这正是 commitReady 路径上的两个选项。由此产出的 ToolExecutionResult 是成功(ToolExecutionSuccess:content + isError:false)与失败(ToolExecutionFailure)的联合,并且可以携带 additionalContextsmetaconcludesTurn

并发按调用来回用 ctx.tools.executionMode(exec) 决定。独占调用形成屏障:调度器在开始前重新读取后续模式,因此注册表的变化可以在当前池排空后把批中某个调用提升为独占。并行调用使用有界滚动池(maxParallelToolCalls),其分发可以重叠,而策略、结果与结果上下文仍保持模型顺序。两种模式都尊重 signal 中止:已开始的调用排空并提交,未开始的调用收到合成结果。

限制过滤

tools.restrict(filter) 按交集为一个作用域过滤全局工具集。被过滤掉的全局工具既不出现在提示中,也拒绝执行——与一个从未存在的工具毫无区别。作用域本地注册在该过滤器之后合并。restrict({}) 因是无操作而被拒绝,保留的 run_code 传输名也不能直接限制(改而限制末端能力工具)。

真实事件

流水线经这些按作用域过滤的事件路由:

事件分发角色
tools/pre-executewaterfall钩子、权限、沙箱的允许/拒绝/询问
tools/executewaterfall围绕分发的超时 / 重试 / 指标
tools/post-executewaterfall接受 / 阻止 / 替换 / 加上下文
tools/resultemit同步冻结的权威结果
sessiontool/call / tool/result会话日志持久化的调用/结果对

延伸阅读

  • 智能体循环 —— 调用 executeToolCallsstep() 循环。
  • 会话管理 —— tool/call / tool/result 事件及其 surface 语义。
  • 作用域系统 —— 作用域工具、遮蔽与按交集限制。
  • 系统提示组装 —— 可见工具 schema 如何进入提示以及 toolOrder
  • 仓库源码:packages/core/tools/src/index.tspackages/core/agent-loop/src/tool-calls.tspackages/core/agent-tool-presentation/src/index.ts
  • 官方脚手架:docs/tool-execution-pipeline.mddocs/tool-catalog.md