跳到主要内容

工具层与沙箱

这章讲什么: 模型「动手」的那一层——工具怎么定义、怎么被挑选挂载,以及最敏感的 shell/代码执行怎么被沙箱关起来。

1. 它要解决的小问题

agent 要能「动手」(检索、执行代码、读写文件/笔记)。两个工程问题:(1) 怎么统一描述工具、自动生成各家 LLM 都吃的 function-calling schema;(2) 让模型执行代码时,怎么不把宿主机暴露给不可信的生成代码

2. 工具层:统一协议 + 上下文门控

2.1 BaseTool 协议

每个工具(内置或插件)都实现 BaseTool(deeptutor/core/tool_protocol.py,BaseTool)。工具用 ToolDefinition + ToolParameter 声明 schema,to_openai_schema 自动转成 OpenAI 格式(tool_protocol.py:60 起)。

一个值得注意的兼容细节:type="array" 的参数,严格的 provider(Gemini、Anthropic)要求必须有 items,否则报 400;OpenAI 却容忍缺失。所以缺 items 时回退成 {"type": "string"},让只声明 ToolParameter(type="array") 的调用也能产出合法 schema(tool_protocol.py:20-26 注释)。适配器工具(如 MCP)的任意 JSON Schema 用 raw_parameters 原样透传,避免重编码丢信息(tool_protocol.py:50-58)。

2.2 ToolRegistry

ToolRegistry(deeptutor/runtime/registry/tool_registry.py,ToolRegistry)是工具查找表:load_builtins 实例化注册所有内置工具,get 取用,还支持别名解析(TOOL_ALIASES)、MCP 热重载时的 unregister、以及延迟工具(deferred_tools,渐进式披露——不一上来全塞给模型)(tool_registry.py:46)。

2.3 上下文门控的工具挂载

不是所有工具一直可见。据 AGENTS.md:只有 4 个用户可开关的工具(brainstorm/web_search/paper_search/reason)常驻 /settings/tools;其余是上下文门控的——聊天能力按 ToolMountFlags(有没有知识库、有没有附件、沙箱可不可用…)自动挂载 rag/read_source/read_memory/write_memory/exec/code_execution/ask_user 等。这让模型每回合看到的工具面是「按当前情境裁过的」,而不是一大坨。

3. 沙箱:三档隔离

这是工具层里安全含量最高的一支。隔离档位定义在 deeptutor/services/sandbox/spec.py:14(IsolationLevel):

档位含义谁能用 shell exec
SYSTEMOS 级强隔离(容器 / bubblewrap 命名空间)普通用户也放行
APPLICATION仅进程内护栏(路径检查 + deny 规则)仅管理员 opt-in
OFF没有可用沙箱永不运行不可信代码

策略门就钉在这个枚举上(spec.py:17-20 注释):普通用户的 shell exec 只在 SYSTEM 隔离下提供;APPLICATION 因为不做 OS 隔离,只允许管理员显式开启;OFF 一律不跑。rank() 给档位排序便于比较(spec.py:27)。

3.1 三个后端

每个后端报告它实际提供的隔离级别(deeptutor/services/sandbox/backends.py 顶部 docstring):

请求执行(ExecRequest:命令 + 挂载 + 资源上限)

┌────┴────────────────┬───────────────────────┐
▼ ▼ ▼
RunnerSidecarBackend BwrapBackend RestrictedSubprocessBackend
(SYSTEM) (SYSTEM) (APPLICATION)
把命令 HTTP 提交给 Linux 裸机用 bwrap 普通子进程 + 清洗 env
独立 runner 容器 挂载命名空间包住 + cwd 路径限制
主 app 永不执行 命令 (本地 dev 降级,如 macOS;
不可信 shell 仅管理员 opt-in)
  • RunnerSidecarBackend:Docker 部署的答案——主 app 保持最小权限、永不执行不可信 shell,把命令转交独立 runner 容器(backends.py:47)。
  • BwrapBackend:Linux 裸机用 bwrap 挂载命名空间。
  • RestrictedSubprocessBackend:本地开发(如 macOS)的降级兜底,只有 env 清洗 + cwd 限制,不做 OS 隔离,所以仅管理员可开。

3.2 资源上限与挂载

每次执行带 ResourceLimits(spec.py:32):timeout_s=30memory_mb=512max_output_chars=10000cpu_seconds=30(best-effort,各后端尽力执行)。Mount(spec.py:42)默认 read_only=True 暴露目录给沙箱。ExecResult.render(spec.py:75)把 stdout/stderr 合并成给模型看的字符串,头尾各截一段防爆上下文。

SandboxService(deeptutor/services/sandbox/service.py,get_sandbox_service/exec_capability_available)是门面:它探测可用后端、报告当前隔离级别,exec 类工具据此决定是否暴露给当前用户。

4. 巧妙之处

  • 隔离级别是一等枚举,安全策略钉在它上面。 「谁能跑 shell」不是散落的 if,而是 IsolationLevel.rank() 的比较——可审计、可在部署文档里讲清(CONTAINERIZATION.md 配套)。
  • 能力探测式优雅降级。 沙箱不可用 → exec 工具就不挂(上下文门控);provider 不支持某参数 → 去掉重试(见 01/02 章)。整个系统的风格是「探测真实能力,按真实能力降级」而不是「假设环境完美」。
  • 延迟工具的渐进式披露。 deferred_tools 让一部分工具不在初始 schema 里,模型需要时再 load_tools 挂上,控制每回合的工具面大小(呼应记忆/RAG 章的「分层控规模」哲学)。

5. 边界与局限

  • APPLICATION 级(纯进程内护栏)并不真正 OS 隔离,作者明确标注它「admin-opt-in only because it does not OS-isolate」(spec.py:19-20)——本地 dev 方便,但不该在多用户生产里对普通用户开。
  • 资源上限是 best-effort,具体强度随后端而异(spec.py:33 注释)。

6. 代码地图

主题文件符号
工具协议deeptutor/core/tool_protocol.pyBaseToolToolDefinitionToolParameterto_openai_schema
工具注册表deeptutor/runtime/registry/tool_registry.pyToolRegistryload_builtinsdeferred_tools
内置工具集deeptutor/tools/rag_tool.pyexec_tool.pyweb_search.pyask_user.py
沙箱隔离档位deeptutor/services/sandbox/spec.pyIsolationLevelResourceLimitsMountExecResult
沙箱后端deeptutor/services/sandbox/backends.pyRunnerSidecarBackendBwrapBackendRestrictedSubprocessBackend
沙箱门面deeptutor/services/sandbox/service.pySandboxServiceget_sandbox_serviceexec_capability_available