Agent 运行机制全景:从工具调用到沙箱隔离

第一部分:Agent 的工具系统

1.1 先搞清楚:工具是用来干嘛的

大模型(LLM)有一个先天缺陷------它只会输出文字。

它能写诗、能解释代码,但它做不了三件事:

LLM 的短板 需要的工具
碰不到外界信息 读取类工具
动不了真实世界 执行/写入类工具
记不住跨轮的事 记忆/状态类工具

所有 agent 工具,本质都是在补 LLM 的这三个洞。

1.2 工具分类全景

类别 典型工具 干什么 类比
文件读写 Read / Write / Edit / Glob / Grep 看文件、写文件、改文件、按名找、按内容搜 眼睛 + 笔 + 字典
命令执行 Bash / Shell 跑 shell、编译、装包、起服务 一双手
网络访问 WebFetch / WebSearch 抓网页、搜互联网 望远镜 + 问路人
信息检索 语义搜索 / 知识库 / 代码检索 在自己的资料里找 翻自家档案柜
外部集成 MCP 类工具 调第三方系统 API 各种专用接头
执行/计算 代码解释器 / LSP / Notebook 真跑代码、要类型信息 计算器 + 编译器
人机交互 提问 / 确认 / 通知 / 剪贴板 问你、弹确认、发通知 开口说话
多媒体生成 图像生成 / 视频生成 生成图片视频 画笔 + 摄像机

1.3 一个反直觉的设计:为什么文件操作要单独封装

既然有 Bash,cat、sed、find 都能干,为什么还要拆出 Read/Write/Edit/Glob/Grep 五个专用工具?

专用工具 为什么不直接用 shell
Read cat 把大文件一次糊你一脸;Read 支持分页、行号、按段读
Edit sed 改文件极易改错且不可逆;Edit 做精确串替换 + 校验
Glob find 语法繁琐;Glob 一眼 **/*.ts 就懂
Grep grep 要拼参数;Grep 封装成结构化结果

规律:能用 shell 干的事,如果高频且容易出错,就会被封装成专用工具。

这就是工具设计的第一原则:不是"能不能做",而是"值不值得专门做一层"。


第二部分:内置工具 vs MCP 工具

2.1 两者的本质区别

维度 内置工具 MCP 工具
谁提供 Agent 框架自产自销 第三方服务
部署位置 同进程内 外部进程/远程
要配置吗 通常不用 通常要
调用方式 本地函数直接调用 网络 RPC
速度 纳秒~毫秒 毫秒~百毫秒
用户感知度 低(像空气) 高(像家具)

一个关键认知:

内置工具 = agent 天生的器官(手、眼、嘴)

MCP 工具 = agent 外接的假肢/外设(要插、要装驱动)

2.2 感知度公式

为什么大家天天聊 MCP,却从不聊"内置 Read 工具"?

感知度 = 用户付出的"认知与操作成本" × 能力增量的"可见度"

  • 内置工具:零配置(成本=0)→ 无感
  • MCP 工具:要装要配(成本高)且接进来的是你原本没有的能力(增量显眼)→ 双倍存在感

注意因果方向 :不是"因为要配所以是外接的",而是"因为它是外接的,所以才要配"。配置只是"外接"这个事实的影子。

2.3 MCP 统一了什么,没统一什么

这是最容易被误解的点,必须钉死:

层面 有标准吗 说明
工具怎么实现 ❌ 没有 各家自己写函数
工具怎么描述给模型 ✅ 大体有 JSON Schema(名字、参数、描述)
工具怎么从外部接入 ✅ 有 MCP 协议

用餐厅打比方:

  • JSON Schema = 菜单怎么写(格式统一)
  • MCP = 餐厅怎么对外营业(点单流程统一)
  • 工具实现 = 后厨怎么做菜(各家自便,没人管)

MCP 的价值不是"统一实现",而是"统一了外部工具被 agent 发现和调用的门槛"。


第三部分:调度链路------一次工具调用是怎么发生的

3.1 LLM 只"决定",不"执行"

这是全篇最核心的一句话。 LLM 自己没有任何执行力 。它输出的是一段结构化 JSON:

json 复制代码
{
  "role": "assistant",
  "content": null,
  "tool_calls": [{
    "id": "call_abc123",
    "type": "function",
    "function": {
      "name": "Read",
      "arguments": "{\"path\": \"pom.xml\", \"limit\": 50}"
    }
  }]
}

注意:这里面没有"执行"二字。它只是"说了一句想调 Read"。 剩下的全交给 agent 运行时。

3.2 完整调度链路

#mermaid-svg-2D8WjiVOcHfxS2Li{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2D8WjiVOcHfxS2Li .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2D8WjiVOcHfxS2Li .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2D8WjiVOcHfxS2Li .error-icon{fill:#552222;}#mermaid-svg-2D8WjiVOcHfxS2Li .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2D8WjiVOcHfxS2Li .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2D8WjiVOcHfxS2Li .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2D8WjiVOcHfxS2Li .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2D8WjiVOcHfxS2Li .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2D8WjiVOcHfxS2Li .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2D8WjiVOcHfxS2Li .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2D8WjiVOcHfxS2Li .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2D8WjiVOcHfxS2Li .marker.cross{stroke:#333333;}#mermaid-svg-2D8WjiVOcHfxS2Li svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2D8WjiVOcHfxS2Li p{margin:0;}#mermaid-svg-2D8WjiVOcHfxS2Li .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2D8WjiVOcHfxS2Li .cluster-label text{fill:#333;}#mermaid-svg-2D8WjiVOcHfxS2Li .cluster-label span{color:#333;}#mermaid-svg-2D8WjiVOcHfxS2Li .cluster-label span p{background-color:transparent;}#mermaid-svg-2D8WjiVOcHfxS2Li .label text,#mermaid-svg-2D8WjiVOcHfxS2Li span{fill:#333;color:#333;}#mermaid-svg-2D8WjiVOcHfxS2Li .node rect,#mermaid-svg-2D8WjiVOcHfxS2Li .node circle,#mermaid-svg-2D8WjiVOcHfxS2Li .node ellipse,#mermaid-svg-2D8WjiVOcHfxS2Li .node polygon,#mermaid-svg-2D8WjiVOcHfxS2Li .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2D8WjiVOcHfxS2Li .rough-node .label text,#mermaid-svg-2D8WjiVOcHfxS2Li .node .label text,#mermaid-svg-2D8WjiVOcHfxS2Li .image-shape .label,#mermaid-svg-2D8WjiVOcHfxS2Li .icon-shape .label{text-anchor:middle;}#mermaid-svg-2D8WjiVOcHfxS2Li .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2D8WjiVOcHfxS2Li .rough-node .label,#mermaid-svg-2D8WjiVOcHfxS2Li .node .label,#mermaid-svg-2D8WjiVOcHfxS2Li .image-shape .label,#mermaid-svg-2D8WjiVOcHfxS2Li .icon-shape .label{text-align:center;}#mermaid-svg-2D8WjiVOcHfxS2Li .node.clickable{cursor:pointer;}#mermaid-svg-2D8WjiVOcHfxS2Li .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2D8WjiVOcHfxS2Li .arrowheadPath{fill:#333333;}#mermaid-svg-2D8WjiVOcHfxS2Li .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2D8WjiVOcHfxS2Li .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2D8WjiVOcHfxS2Li .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2D8WjiVOcHfxS2Li .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2D8WjiVOcHfxS2Li .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2D8WjiVOcHfxS2Li .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2D8WjiVOcHfxS2Li .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2D8WjiVOcHfxS2Li .cluster text{fill:#333;}#mermaid-svg-2D8WjiVOcHfxS2Li .cluster span{color:#333;}#mermaid-svg-2D8WjiVOcHfxS2Li div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2D8WjiVOcHfxS2Li .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2D8WjiVOcHfxS2Li rect.text{fill:none;stroke-width:0;}#mermaid-svg-2D8WjiVOcHfxS2Li .icon-shape,#mermaid-svg-2D8WjiVOcHfxS2Li .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2D8WjiVOcHfxS2Li .icon-shape p,#mermaid-svg-2D8WjiVOcHfxS2Li .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2D8WjiVOcHfxS2Li .icon-shape .label rect,#mermaid-svg-2D8WjiVOcHfxS2Li .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2D8WjiVOcHfxS2Li .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2D8WjiVOcHfxS2Li .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2D8WjiVOcHfxS2Li :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} LLM 输出 tool_calls

{name:'Read', args:{...}}
agent 运行时解析这段文本

拿到工具名 'Read'
在【本地工具注册表】按名查找

registry'Read'
命中 → 直接调用本地函数对象

ReadFn(path, limit)
函数体内: 读文件/加行号/裁剪
结果包成一条 tool 消息
追加进对话历史,重新发给 LLM
LLM 看到结果,继续下一步

3.3 关键:内置工具"怎么被调起来"

答案:就是一次普通的本地函数调用(in-process function call)。

内置工具 MCP 工具
工具是什么 内存里一个函数对象 一个远程服务
怎么找到 查本地注册表(一个 dict) 走 MCP 协议问 server
怎么调用 fn(**args) 直接调 发 JSON-RPC 网络请求
跨进程吗 ❌ 同进程 ✅ 跨进程/跨机

3.4 靠什么实现的:工具注册表

agent 框架启动时,把内置工具登记进一张表:

python 复制代码
# 伪代码
registry = {}

def register_tool(name, description, params_schema, fn):
    registry[name] = {
        "description": description,
        "schema": params_schema,  # 发给 LLM 看的(菜单)
        "fn": fn,                 # 真正干活的(后厨)
    }

register_tool("Read",  "...", {...}, read_impl)
register_tool("Write", "...", {...}, write_impl)
register_tool("Bash",  "...", {...}, bash_impl)

模型报个名,运行时用它去字典里捞出真函数,调它就完事。
#mermaid-svg-3PFhLkM5COUA6bRj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3PFhLkM5COUA6bRj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3PFhLkM5COUA6bRj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3PFhLkM5COUA6bRj .error-icon{fill:#552222;}#mermaid-svg-3PFhLkM5COUA6bRj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3PFhLkM5COUA6bRj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3PFhLkM5COUA6bRj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3PFhLkM5COUA6bRj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3PFhLkM5COUA6bRj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3PFhLkM5COUA6bRj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3PFhLkM5COUA6bRj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3PFhLkM5COUA6bRj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3PFhLkM5COUA6bRj .marker.cross{stroke:#333333;}#mermaid-svg-3PFhLkM5COUA6bRj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3PFhLkM5COUA6bRj p{margin:0;}#mermaid-svg-3PFhLkM5COUA6bRj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3PFhLkM5COUA6bRj .cluster-label text{fill:#333;}#mermaid-svg-3PFhLkM5COUA6bRj .cluster-label span{color:#333;}#mermaid-svg-3PFhLkM5COUA6bRj .cluster-label span p{background-color:transparent;}#mermaid-svg-3PFhLkM5COUA6bRj .label text,#mermaid-svg-3PFhLkM5COUA6bRj span{fill:#333;color:#333;}#mermaid-svg-3PFhLkM5COUA6bRj .node rect,#mermaid-svg-3PFhLkM5COUA6bRj .node circle,#mermaid-svg-3PFhLkM5COUA6bRj .node ellipse,#mermaid-svg-3PFhLkM5COUA6bRj .node polygon,#mermaid-svg-3PFhLkM5COUA6bRj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3PFhLkM5COUA6bRj .rough-node .label text,#mermaid-svg-3PFhLkM5COUA6bRj .node .label text,#mermaid-svg-3PFhLkM5COUA6bRj .image-shape .label,#mermaid-svg-3PFhLkM5COUA6bRj .icon-shape .label{text-anchor:middle;}#mermaid-svg-3PFhLkM5COUA6bRj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3PFhLkM5COUA6bRj .rough-node .label,#mermaid-svg-3PFhLkM5COUA6bRj .node .label,#mermaid-svg-3PFhLkM5COUA6bRj .image-shape .label,#mermaid-svg-3PFhLkM5COUA6bRj .icon-shape .label{text-align:center;}#mermaid-svg-3PFhLkM5COUA6bRj .node.clickable{cursor:pointer;}#mermaid-svg-3PFhLkM5COUA6bRj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3PFhLkM5COUA6bRj .arrowheadPath{fill:#333333;}#mermaid-svg-3PFhLkM5COUA6bRj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3PFhLkM5COUA6bRj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3PFhLkM5COUA6bRj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3PFhLkM5COUA6bRj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3PFhLkM5COUA6bRj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3PFhLkM5COUA6bRj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3PFhLkM5COUA6bRj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3PFhLkM5COUA6bRj .cluster text{fill:#333;}#mermaid-svg-3PFhLkM5COUA6bRj .cluster span{color:#333;}#mermaid-svg-3PFhLkM5COUA6bRj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3PFhLkM5COUA6bRj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3PFhLkM5COUA6bRj rect.text{fill:none;stroke-width:0;}#mermaid-svg-3PFhLkM5COUA6bRj .icon-shape,#mermaid-svg-3PFhLkM5COUA6bRj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3PFhLkM5COUA6bRj .icon-shape p,#mermaid-svg-3PFhLkM5COUA6bRj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3PFhLkM5COUA6bRj .icon-shape .label rect,#mermaid-svg-3PFhLkM5COUA6bRj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3PFhLkM5COUA6bRj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3PFhLkM5COUA6bRj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3PFhLkM5COUA6bRj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} agent运行时
模型侧
tool_calls (纯文本JSON)
读文件/Bash
JSON-RPC
tool 消息回灌
LLM

只看 schema

只会决定
调度器

registryname.fn
内置 fn

本地函数直调
MCP 代理 fn

发网络请求
结果

3.5 两种工具怎么在同一套调度里共存

对调度器来说,MCP 工具和内置工具结构一样,只是 fn 的实现不同:

复制代码
registry = {
  "Read":              { schema, fn: <本地函数> },      # 内置
  "github_create_pr":  { schema, fn: <MCP 代理函数> }   # 外部
}

统一在调度层(都从注册表捞、都按名调);分叉在执行层(本地直调 vs 网络 RPC)。


第四部分:沙箱------Agent 的"房子"

4.1 什么是沙箱

沙箱 = 一块隔离出来的、可以随便折腾坏了也不影响别人的执行环境。

4.2 沙箱解决什么问题

场景 不隔离的后果
用户让 agent rm -rf / 把宿主服务器删了
agent 跑了个挖矿脚本 整台机器被拖垮
用户 A 的文件 用户 B 能直接读到
agent 装了 100 个包 污染全局环境

对应沙箱四个职责:

职责 隔离谁和谁
安全隔离 agent 与宿主系统
资源隔离 限制 CPU/内存/磁盘
租户隔离 用户 A 与用户 B
环境隔离 项目 A 与项目 B

4.3 隔离技术分级

手段 隔离粒度 通俗解释 例子
进程级 弱 换个用户/目录跑 chroot
容器级 中 共享内核,独立文件系统/网络 Docker
虚机级 强 独立内核,完全另一台机器 KVM、Firecracker
函数级 特化 每次执行现开微虚机 AWS Lambda

第五部分:★ 两种沙箱------最容易混的核心概念

"沙箱"这个词被用在两个层级,同名不同物。这是全篇最容易混淆、也最需要厘清的一点。

5.1 对照表

A. Agent 运行沙箱 B. 代码执行沙箱
装的是谁 整个 agent 程序 一段临时脚本
目的 隔离 agent 与宿主系统 隔离不可信代码与 agent 自己
生命周期 跟随整个会话(长) 跟随单次执行(短,跑完就扔)
谁需要它 云端 agent 平台 任何"要跑外部代码"的场景
典型技术 Docker/VM(整个程序装进去) 受限解释器、子进程、微VM
方向 agent 住在沙箱里(租客) agent 把代码扔进沙箱(房东)

看出区别了吗?方向刚好反过来!

  • A:agent 是租客,住在沙箱里
  • B:agent 是房东,把不可信的代码关进沙箱

5.2 二者的嵌套关系

#mermaid-svg-dafVainfS9eoqjzn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dafVainfS9eoqjzn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dafVainfS9eoqjzn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dafVainfS9eoqjzn .error-icon{fill:#552222;}#mermaid-svg-dafVainfS9eoqjzn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dafVainfS9eoqjzn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dafVainfS9eoqjzn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dafVainfS9eoqjzn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dafVainfS9eoqjzn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dafVainfS9eoqjzn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dafVainfS9eoqjzn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dafVainfS9eoqjzn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dafVainfS9eoqjzn .marker.cross{stroke:#333333;}#mermaid-svg-dafVainfS9eoqjzn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dafVainfS9eoqjzn p{margin:0;}#mermaid-svg-dafVainfS9eoqjzn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dafVainfS9eoqjzn .cluster-label text{fill:#333;}#mermaid-svg-dafVainfS9eoqjzn .cluster-label span{color:#333;}#mermaid-svg-dafVainfS9eoqjzn .cluster-label span p{background-color:transparent;}#mermaid-svg-dafVainfS9eoqjzn .label text,#mermaid-svg-dafVainfS9eoqjzn span{fill:#333;color:#333;}#mermaid-svg-dafVainfS9eoqjzn .node rect,#mermaid-svg-dafVainfS9eoqjzn .node circle,#mermaid-svg-dafVainfS9eoqjzn .node ellipse,#mermaid-svg-dafVainfS9eoqjzn .node polygon,#mermaid-svg-dafVainfS9eoqjzn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dafVainfS9eoqjzn .rough-node .label text,#mermaid-svg-dafVainfS9eoqjzn .node .label text,#mermaid-svg-dafVainfS9eoqjzn .image-shape .label,#mermaid-svg-dafVainfS9eoqjzn .icon-shape .label{text-anchor:middle;}#mermaid-svg-dafVainfS9eoqjzn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dafVainfS9eoqjzn .rough-node .label,#mermaid-svg-dafVainfS9eoqjzn .node .label,#mermaid-svg-dafVainfS9eoqjzn .image-shape .label,#mermaid-svg-dafVainfS9eoqjzn .icon-shape .label{text-align:center;}#mermaid-svg-dafVainfS9eoqjzn .node.clickable{cursor:pointer;}#mermaid-svg-dafVainfS9eoqjzn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dafVainfS9eoqjzn .arrowheadPath{fill:#333333;}#mermaid-svg-dafVainfS9eoqjzn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dafVainfS9eoqjzn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dafVainfS9eoqjzn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dafVainfS9eoqjzn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dafVainfS9eoqjzn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dafVainfS9eoqjzn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dafVainfS9eoqjzn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dafVainfS9eoqjzn .cluster text{fill:#333;}#mermaid-svg-dafVainfS9eoqjzn .cluster span{color:#333;}#mermaid-svg-dafVainfS9eoqjzn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dafVainfS9eoqjzn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dafVainfS9eoqjzn rect.text{fill:none;stroke-width:0;}#mermaid-svg-dafVainfS9eoqjzn .icon-shape,#mermaid-svg-dafVainfS9eoqjzn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dafVainfS9eoqjzn .icon-shape p,#mermaid-svg-dafVainfS9eoqjzn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dafVainfS9eoqjzn .icon-shape .label rect,#mermaid-svg-dafVainfS9eoqjzn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dafVainfS9eoqjzn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dafVainfS9eoqjzn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dafVainfS9eoqjzn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} A. Agent 运行沙箱(大盒子,装整个agent)
需要执行代码
返回结果
B. 代码执行沙箱(小盒子,装临时脚本)
跑那段 Python/JS 脚本
agent 程序

大盒子套小盒子:agent 住在 A 里,A 里再开 B 去跑不可信脚本。

5.3 两种沙箱,你要写代码实现吗

沙箱 你要写代码实现吗 说明
A. Agent 运行沙箱 ❌ 不写 部署层(Docker/K8s/云)提供
B. 代码执行沙箱 ⚠️ 可能要 若你要支持"跑外部代码",你得自己动手隔离

B 是唯一一个你可能真需要在代码里"动手"的沙箱。


第六部分:文件进出的真相

6.1 一个常见直觉 vs 真相

直觉:文件不在沙箱里,是不是"复制一份进去 → 改好 → 自动写回本地"?

真相 :要分两种 agent 看,而且两种都不是这个模型。

模式 文件在哪 是"复制→改→回写"吗
云端 agent 上传的文件复制进沙箱 ✅ 进是复制;但改写后不自动写回,要你手动下载
本地 agent 文件本就在本地盘,直接原地读写 ❌ 根本没有复制这回事

6.2 云端 agent 的文件流转

#mermaid-svg-QhZuYc5NY0iGGArl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QhZuYc5NY0iGGArl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QhZuYc5NY0iGGArl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QhZuYc5NY0iGGArl .error-icon{fill:#552222;}#mermaid-svg-QhZuYc5NY0iGGArl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QhZuYc5NY0iGGArl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QhZuYc5NY0iGGArl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QhZuYc5NY0iGGArl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QhZuYc5NY0iGGArl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QhZuYc5NY0iGGArl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QhZuYc5NY0iGGArl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QhZuYc5NY0iGGArl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QhZuYc5NY0iGGArl .marker.cross{stroke:#333333;}#mermaid-svg-QhZuYc5NY0iGGArl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QhZuYc5NY0iGGArl p{margin:0;}#mermaid-svg-QhZuYc5NY0iGGArl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QhZuYc5NY0iGGArl .cluster-label text{fill:#333;}#mermaid-svg-QhZuYc5NY0iGGArl .cluster-label span{color:#333;}#mermaid-svg-QhZuYc5NY0iGGArl .cluster-label span p{background-color:transparent;}#mermaid-svg-QhZuYc5NY0iGGArl .label text,#mermaid-svg-QhZuYc5NY0iGGArl span{fill:#333;color:#333;}#mermaid-svg-QhZuYc5NY0iGGArl .node rect,#mermaid-svg-QhZuYc5NY0iGGArl .node circle,#mermaid-svg-QhZuYc5NY0iGGArl .node ellipse,#mermaid-svg-QhZuYc5NY0iGGArl .node polygon,#mermaid-svg-QhZuYc5NY0iGGArl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QhZuYc5NY0iGGArl .rough-node .label text,#mermaid-svg-QhZuYc5NY0iGGArl .node .label text,#mermaid-svg-QhZuYc5NY0iGGArl .image-shape .label,#mermaid-svg-QhZuYc5NY0iGGArl .icon-shape .label{text-anchor:middle;}#mermaid-svg-QhZuYc5NY0iGGArl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QhZuYc5NY0iGGArl .rough-node .label,#mermaid-svg-QhZuYc5NY0iGGArl .node .label,#mermaid-svg-QhZuYc5NY0iGGArl .image-shape .label,#mermaid-svg-QhZuYc5NY0iGGArl .icon-shape .label{text-align:center;}#mermaid-svg-QhZuYc5NY0iGGArl .node.clickable{cursor:pointer;}#mermaid-svg-QhZuYc5NY0iGGArl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QhZuYc5NY0iGGArl .arrowheadPath{fill:#333333;}#mermaid-svg-QhZuYc5NY0iGGArl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QhZuYc5NY0iGGArl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QhZuYc5NY0iGGArl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QhZuYc5NY0iGGArl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QhZuYc5NY0iGGArl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QhZuYc5NY0iGGArl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QhZuYc5NY0iGGArl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QhZuYc5NY0iGGArl .cluster text{fill:#333;}#mermaid-svg-QhZuYc5NY0iGGArl .cluster span{color:#333;}#mermaid-svg-QhZuYc5NY0iGGArl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QhZuYc5NY0iGGArl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QhZuYc5NY0iGGArl rect.text{fill:none;stroke-width:0;}#mermaid-svg-QhZuYc5NY0iGGArl .icon-shape,#mermaid-svg-QhZuYc5NY0iGGArl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QhZuYc5NY0iGGArl .icon-shape p,#mermaid-svg-QhZuYc5NY0iGGArl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QhZuYc5NY0iGGArl .icon-shape .label rect,#mermaid-svg-QhZuYc5NY0iGGArl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QhZuYc5NY0iGGArl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QhZuYc5NY0iGGArl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QhZuYc5NY0iGGArl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ① 上传(复制)
② Read/Write 原地改
③ 交付/下载(手动)
你设备里的 report.docx
/root/uploads/report.docx

(沙箱内)
沙箱内新文件
回到你设备

  • 进:上传 = 复制进沙箱
  • 改:全程只动沙箱内那一份
  • 出 :没有自动回写! 必须靠"交付/下载"动作

6.3 本地 agent:根本不复制

复制代码
你本地: E:\project\pom.xml
      ↓
agent 运行时(跑在你电脑上) → 直接 open("E:\\project\\pom.xml")
      ↓
原地读、原地改、原地写  ← 没有副本

6.4 沙箱隔的到底是什么

沙箱隔的从来不是"文件",而是"进程能访问的范围"。

  • 云端:沙箱是独立 VM,你的文件不在里面 ,所以必须先搬进去(上传)
  • 本地:agent 进程和你的盘在同一系统,没有墙,直接访问

"复制进去"不是沙箱的要求,是"你的文件恰好在沙箱外"导致的必然结果。

6.5 实战坑

坑 说明
云端"改写不落盘" 改了没交付 → 改动还在沙箱里,你那边纹丝不动
本地"没有后悔药" 直接改真文件,误操作难恢复

第七部分:程序、环境、沙箱三者关系

7.1 纠正一个直觉错位

错误心智模型:

复制代码
agent程序(A环境) ──跳──► 沙箱(B环境) ──执行工具──► 回来

正确心智模型:

复制代码
       ┌──── 同一个环境(沙箱=宿主)────┐
       │                                │
       │   agent程序  ── 直接调 ──► 工具 │
       │                                │
       └────────────────────────────────┘

不存在"跨环境跳"这一步。agent 程序和它的工具都住在同一个环境里。

7.2 agent 程序 vs 沙箱:是不是一回事

答案:不是,但绑定共存。

概念 是"谁" 是"在哪"
agent 程序 ✅ 进程 ---
沙箱 --- ✅ 环境

比方:agent 程序 = 你这个人;沙箱 = 你上班的办公室。

你在办公室办公,但你 ≠ 办公室。

运行视角:绝大多数部署里它俩"绑定共存"------沙箱销毁,agent 也停;agent 停,沙箱回收。

7.3 宿主 ≠ 沙箱(关键区分)

概念 是什么
宿主环境 程序跑在哪台机/哪个系统(人人都有)
沙箱 这个环境是否被隔离(要看有没有隔离手段)
部署方式 宿主是? 是沙箱吗
直接跑自己笔记本 笔记本 ❌ 不是(无隔离)
跑在 Docker 容器 容器 ✅ 是
跑在云 VM 云 VM ✅ 是
跑在服务器裸机 服务器 ❌ 不是

"部署在哪" → 得到宿主环境;"有没有隔离" → 才决定是不是沙箱。

7.4 沙箱需要 agent 代码去实现吗

主线结论:

沙箱不需要 agent 程序去代码实现。它由部署/平台层提供,程序只是"住进去"。

python 复制代码
# 你的 agent 代码里,大概就这些
String result = exec("ls -la");        # 执行命令
String content = readFile("a.txt");    # 读文件

你只管"调工具",压根不管"在哪执行"。 因为环境在你程序启动前就已存在。

用比方钉死:

你写了个程序(像个打工人),你会去写"给我的办公室装防弹门"的代码吗?不会。办公室是房东(平台/运维)提供的。

隔离级别 谁提供 agent 代码写啥
进程级 部署命令 啥都不用写
容器级 平台/运维写 Dockerfile 啥都不用写
虚机级 云平台 啥都不用写
函数级 云厂商 啥都不用写

第八部分:代码执行沙箱(B)的实现

8.1 实现方案全景

方案 隔离强度 启动速度 难度 适用场景
子进程 + 资源限制 弱 极快 低 可信代码、内部脚本
受限解释器 弱 快 中 Python 表达式求值
容器(Docker) 强 中 中 通用,主流选择
微虚机(Firecracker/gVisor) 很强 较快 高 多租户 SaaS
云函数(Lambda) 强 慢 低 不想自运维
WASM 强 极快 中 JS/WASM 生态

8.2 子进程方案(最轻量)

python 复制代码
import subprocess, resource

def run_sandboxed(code):
    def preexec():
        resource.setrlimit(resource.RLIMIT_CPU, (5, 5))      # 最多5秒CPU
        resource.setrlimit(resource.RLIMIT_AS, (256<<20,))   # 最多256MB内存
    r = subprocess.run(["python3", "-c", code],
                       capture_output=True, timeout=10, preexec_fn=preexec)
    return r.stdout, r.stderr
  • 能防:跑飞、死循环、吃内存
  • 不能防:读写文件、联网(同用户同命名空间)
  • 结论 :只防"跑飞",不防"干坏事"

8.3 容器方案(主流)

python 复制代码
import docker
client = docker.from_env()

def run_in_container(code):
    return client.containers.run(
        "my-sandbox:latest",
        command=["python", "-c", code],
        network_disabled=True,      # ★ 断网
        mem_limit="256m",           # ★ 限内存
        cpu_quota=50000,            # ★ 限CPU
        read_only=True,             # ★ 只读根文件系统
        volumes={"/tmp/sbx": {"bind": "/work", "mode": "rw"}},
        remove=True,                # ★ 跑完自动删
    )

8.4 沙箱一般在做哪些事

维度 沙箱要干的活 手段
文件系统 只碰指定目录,其余只读/不可见 挂载隔离、只读层
网络 默认断网,或白名单 network_disabled、iptables
CPU 限时间,防死循环 cgroups、rlimit、超时
内存 限大小,防吃爆 cgroups、rlimit
磁盘 限写入量 quota、临时目录
进程 限数量、限权限 降权、seccomp
系统调用 只放行必要 syscall seccomp-bpf
生命周期 超时杀、跑完销毁 remove、容器池
输入输出 只传代码进、只取结果出 标准输入输出管道
审计 记录跑了什么 日志

沙箱 = 一整套"限制"的集合体,不是单一功能。

8.5 实战坑

  1. 别用 eval()/exec() 直接跑------零隔离
  2. timeout 必须设------否则死循环挂死服务
  3. 网络默认关------否则脚本能外传数据
  4. 跑完要销毁------别复用同一沙箱跑不同用户代码
  5. 输出要限量 ------print 100 万行撑爆内存
  6. 别信"只读"------要真只读(挂载只读层)

第九部分:容器的创建与环境准备

9.1 容器自带 Python 吗

❌ 不是。容器是个空壳,里面有什么全看镜像。

镜像 里面有啥
python:3.11-slim ✅ Python 3.11
node:20 ✅ Node.js
ubuntu:22.04 ❌ 啥都没有
alpine ⚠️ 极简

"容器自带环境"是错觉------是你选了带环境的镜像。

Docker 隔离模型 :容器 = 镜像(文件系统快照)+ 一层可写层。

9.2 镜像要提前准备

dockerfile 复制代码
# Dockerfile.sandbox
FROM python:3.11-slim
RUN pip install numpy pandas requests   # ★ 预装依赖
WORKDIR /work
bash 复制代码
docker build -t my-agent-sandbox:latest -f Dockerfile.sandbox .

9.3 ★ 每次新建 vs 复用

默认做法:每次调用新建一个容器,跑完销毁(安全优先)。

每次新建的好处 复用同一个的坏处
用户 A 的痕迹不留给 B A 的文件/内存/环境变量留在里面
跑挂的污染只影响这一个 一个脚本改环境,影响后续所有
状态干净可预测 状态漂移,结果不可复现

但"每次新建"有冷启动代价(几百毫秒~几秒),优化手段:

优化方案 做法 牺牲
容器池(Pool) 预起 N 个待命,借出归还 归还前必须清理
快照/预热 微虚机快照,秒起 复杂度高
热进程复用 同任务内复用 仅限可信同源
WASM 微秒级启动 生态限制

关键原则:复用的边界 = 信任的边界。


第十部分:Agent 在沙箱里拉起沙箱容器

10.1 问题本质:DinD

#mermaid-svg-vGeZpU8wCupJmKxl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vGeZpU8wCupJmKxl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vGeZpU8wCupJmKxl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vGeZpU8wCupJmKxl .error-icon{fill:#552222;}#mermaid-svg-vGeZpU8wCupJmKxl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vGeZpU8wCupJmKxl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vGeZpU8wCupJmKxl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vGeZpU8wCupJmKxl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vGeZpU8wCupJmKxl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vGeZpU8wCupJmKxl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vGeZpU8wCupJmKxl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vGeZpU8wCupJmKxl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vGeZpU8wCupJmKxl .marker.cross{stroke:#333333;}#mermaid-svg-vGeZpU8wCupJmKxl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vGeZpU8wCupJmKxl p{margin:0;}#mermaid-svg-vGeZpU8wCupJmKxl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vGeZpU8wCupJmKxl .cluster-label text{fill:#333;}#mermaid-svg-vGeZpU8wCupJmKxl .cluster-label span{color:#333;}#mermaid-svg-vGeZpU8wCupJmKxl .cluster-label span p{background-color:transparent;}#mermaid-svg-vGeZpU8wCupJmKxl .label text,#mermaid-svg-vGeZpU8wCupJmKxl span{fill:#333;color:#333;}#mermaid-svg-vGeZpU8wCupJmKxl .node rect,#mermaid-svg-vGeZpU8wCupJmKxl .node circle,#mermaid-svg-vGeZpU8wCupJmKxl .node ellipse,#mermaid-svg-vGeZpU8wCupJmKxl .node polygon,#mermaid-svg-vGeZpU8wCupJmKxl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vGeZpU8wCupJmKxl .rough-node .label text,#mermaid-svg-vGeZpU8wCupJmKxl .node .label text,#mermaid-svg-vGeZpU8wCupJmKxl .image-shape .label,#mermaid-svg-vGeZpU8wCupJmKxl .icon-shape .label{text-anchor:middle;}#mermaid-svg-vGeZpU8wCupJmKxl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vGeZpU8wCupJmKxl .rough-node .label,#mermaid-svg-vGeZpU8wCupJmKxl .node .label,#mermaid-svg-vGeZpU8wCupJmKxl .image-shape .label,#mermaid-svg-vGeZpU8wCupJmKxl .icon-shape .label{text-align:center;}#mermaid-svg-vGeZpU8wCupJmKxl .node.clickable{cursor:pointer;}#mermaid-svg-vGeZpU8wCupJmKxl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vGeZpU8wCupJmKxl .arrowheadPath{fill:#333333;}#mermaid-svg-vGeZpU8wCupJmKxl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vGeZpU8wCupJmKxl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vGeZpU8wCupJmKxl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vGeZpU8wCupJmKxl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vGeZpU8wCupJmKxl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vGeZpU8wCupJmKxl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vGeZpU8wCupJmKxl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vGeZpU8wCupJmKxl .cluster text{fill:#333;}#mermaid-svg-vGeZpU8wCupJmKxl .cluster span{color:#333;}#mermaid-svg-vGeZpU8wCupJmKxl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vGeZpU8wCupJmKxl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vGeZpU8wCupJmKxl rect.text{fill:none;stroke-width:0;}#mermaid-svg-vGeZpU8wCupJmKxl .icon-shape,#mermaid-svg-vGeZpU8wCupJmKxl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vGeZpU8wCupJmKxl .icon-shape p,#mermaid-svg-vGeZpU8wCupJmKxl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vGeZpU8wCupJmKxl .icon-shape .label rect,#mermaid-svg-vGeZpU8wCupJmKxl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vGeZpU8wCupJmKxl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vGeZpU8wCupJmKxl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vGeZpU8wCupJmKxl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 宿主服务器
容器A:你的 agent 程序
想再拉一个容器B ❌
Docker 守护进程 dockerd

问题 :容器里的进程默认摸不到宿主的 Docker 守护进程------这正是隔离生效的表现。

10.2 方案一:挂载 Docker Socket(方便但危险)

yaml 复制代码
services:
  agent:
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock  # ★ 挂进来

⚠️ 致命问题:

能控制 docker.sock = 能控制宿主上的一切。

拿到 socket 可以起 --privileged 容器、挂载宿主根目录、读所有数据。

结论:只适合本地开发。生产别用。

10.3 方案二:DinD 真套娃(不推荐)

在容器 A 里再跑一个独立 dockerd。

  • 优点:和宿主隔离
  • 缺点:需要 --privileged、性能差、缓存难共享

10.4 ★ 方案三:K8s Job(云原生正统)

核心思路:不拉 Docker 容器,而是创建 K8s 资源(Job/Pod)。

python 复制代码
from kubernetes import client, config
config.load_incluster_config()
batch_v1 = client.BatchV1Api()

job = client.V1Job(
    metadata=client.V1ObjectMeta(name=f"sandbox-{uuid4()}"),
    spec=client.V1JobSpec(
        template=client.V1PodTemplateSpec(
            spec=client.V1PodSpec(
                restartPolicy="Never",
                containers=[client.V1Container(
                    name="runner",
                    image="my-agent-sandbox:latest",
                    command=["python", code],
                )],
            )
        ),
        backoffLimit=0,
    )
)
batch_v1.create_namespaced_job(namespace="sandbox-ns", body=job)
优点 说明
权限可控 只需"创建 Job"的 RBAC 权限,不是宿主 root
天然隔离 沙箱 Pod 与 agent Pod 是平级,不碰宿主
资源管控 resources.limits 限 CPU/内存
网络策略 NetworkPolicy 断网
可伸缩 集群资源多就能并发

注意:沙箱 Pod 是跑在 agent Pod "旁边",不是"里面"------平级,不是套娃。

10.5 方案四:云沙箱 API(零运维)

用 E2B、Modal、Daytona 这类服务:

python 复制代码
from e2b import Sandbox
sbx = Sandbox()
result = sbx.run_code(code)
sbx.close()

10.6 选型决策表

方案 安全性 复杂度 适用场景
挂 docker.sock 💀 极低 低 本地开发
DinD 套娃 中 高 老场景,不推荐
K8s Job ✅ 高 中 云原生首选
云沙箱 API ✅ 高 极低 不想运维
gVisor/Kata ✅✅ 极高 高 多租户 SaaS

10.7 ★ 一个必须摆正的因果关系

很多人会说"Docker/K8s 支持通过网络传脚本到远端执行 "------这句话结论对,因果要摆正:

不是"Docker/K8s 提供了远程执行脚本的能力",而是"它们提供了执行能力,而这份执行能力本来就可以通过网络调用"。

它们本身确实自带远程能力:

技术 自带的远程能力
Docker Daemon 支持 TCP 远程连接 (-H tcp://host:2375,不用本地 socket)
K8s API Server 天生就是网络服务,跨机器操作是本职

但三层职责要分清:
#mermaid-svg-josL9bwGeFG9GlaL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-josL9bwGeFG9GlaL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-josL9bwGeFG9GlaL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-josL9bwGeFG9GlaL .error-icon{fill:#552222;}#mermaid-svg-josL9bwGeFG9GlaL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-josL9bwGeFG9GlaL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-josL9bwGeFG9GlaL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-josL9bwGeFG9GlaL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-josL9bwGeFG9GlaL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-josL9bwGeFG9GlaL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-josL9bwGeFG9GlaL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-josL9bwGeFG9GlaL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-josL9bwGeFG9GlaL .marker.cross{stroke:#333333;}#mermaid-svg-josL9bwGeFG9GlaL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-josL9bwGeFG9GlaL p{margin:0;}#mermaid-svg-josL9bwGeFG9GlaL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-josL9bwGeFG9GlaL .cluster-label text{fill:#333;}#mermaid-svg-josL9bwGeFG9GlaL .cluster-label span{color:#333;}#mermaid-svg-josL9bwGeFG9GlaL .cluster-label span p{background-color:transparent;}#mermaid-svg-josL9bwGeFG9GlaL .label text,#mermaid-svg-josL9bwGeFG9GlaL span{fill:#333;color:#333;}#mermaid-svg-josL9bwGeFG9GlaL .node rect,#mermaid-svg-josL9bwGeFG9GlaL .node circle,#mermaid-svg-josL9bwGeFG9GlaL .node ellipse,#mermaid-svg-josL9bwGeFG9GlaL .node polygon,#mermaid-svg-josL9bwGeFG9GlaL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-josL9bwGeFG9GlaL .rough-node .label text,#mermaid-svg-josL9bwGeFG9GlaL .node .label text,#mermaid-svg-josL9bwGeFG9GlaL .image-shape .label,#mermaid-svg-josL9bwGeFG9GlaL .icon-shape .label{text-anchor:middle;}#mermaid-svg-josL9bwGeFG9GlaL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-josL9bwGeFG9GlaL .rough-node .label,#mermaid-svg-josL9bwGeFG9GlaL .node .label,#mermaid-svg-josL9bwGeFG9GlaL .image-shape .label,#mermaid-svg-josL9bwGeFG9GlaL .icon-shape .label{text-align:center;}#mermaid-svg-josL9bwGeFG9GlaL .node.clickable{cursor:pointer;}#mermaid-svg-josL9bwGeFG9GlaL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-josL9bwGeFG9GlaL .arrowheadPath{fill:#333333;}#mermaid-svg-josL9bwGeFG9GlaL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-josL9bwGeFG9GlaL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-josL9bwGeFG9GlaL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-josL9bwGeFG9GlaL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-josL9bwGeFG9GlaL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-josL9bwGeFG9GlaL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-josL9bwGeFG9GlaL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-josL9bwGeFG9GlaL .cluster text{fill:#333;}#mermaid-svg-josL9bwGeFG9GlaL .cluster span{color:#333;}#mermaid-svg-josL9bwGeFG9GlaL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-josL9bwGeFG9GlaL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-josL9bwGeFG9GlaL rect.text{fill:none;stroke-width:0;}#mermaid-svg-josL9bwGeFG9GlaL .icon-shape,#mermaid-svg-josL9bwGeFG9GlaL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-josL9bwGeFG9GlaL .icon-shape p,#mermaid-svg-josL9bwGeFG9GlaL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-josL9bwGeFG9GlaL .icon-shape .label rect,#mermaid-svg-josL9bwGeFG9GlaL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-josL9bwGeFG9GlaL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-josL9bwGeFG9GlaL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-josL9bwGeFG9GlaL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ③ 你的应用层(业务语义)
借用
操作
① 执行环境层(本地能力)
容器运行时

真正跑代码
② 远程接口层(网络能力)
Docker Daemon TCP
K8s API Server
沙箱服务 /execute

'传脚本→跑→返回'

层 职责 是"网络"的吗
容器运行时 提供执行环境 本地能力
远程接口(Daemon TCP / K8s API) 让环境可远程调用 网络能力
你的应用(沙箱服务) 把"传脚本→执行→返回"封装成业务接口 业务能力

Docker/K8s 覆盖 ① 和 ②;"传脚本执行"这个语义在 ③,是你加的。

⚠️ 而且它们的原生接口是"控制面接口",极度危险:

直接暴露 后果
Docker Daemon 2375 端口 等于把宿主 root 权限给了全网
K8s API 无鉴权 谁都能删你的集群

所以现实里没人裸暴露它们,而是在上面包一层受控的"沙箱服务",对外只暴露 /execute。 这正是第十二部分要讲的东西。

精确措辞建议(写博客时用):

  • ❌ 不准确:"Docker/K8s 支持网络传输脚本去执行"
  • ✅ 准确:"Docker/K8s 提供可远程调用的执行环境 ;在其之上可以构建网络代码执行服务"

第十一部分:沙箱的依赖管理

11.1 Pod 环境也是提前定义的

Pod = 容器组合,容器 = 从镜像实例化。环境全靠镜像定义。

yaml 复制代码
spec:
  containers:
    - name: runner
      image: my-sandbox:latest   # ★ 环境全在这个镜像里

11.2 依赖不全怎么办------四层解法

方案 做法 优劣
1. 预装大全套 常用依赖全塞镜像 开箱即用;镜像巨大、总有漏
2. 多镜像分层 按场景准备多个镜像按需选 主流做法
3. 动态构建镜像 按请求依赖临时 build 最灵活;首次慢
4. 错误喂回 LLM 让 LLM 自己改代码补依赖 ★ agent 特有的自愈

11.3 动态构建镜像:不是每次真 build

核心洞察:用"依赖清单的哈希"当镜像 tag,命中就复用,没命中才 build。

python 复制代码
import hashlib

def get_or_build_image(requirements):
    # 1. 依赖清单排序后算哈希 → 当 tag
    key = hashlib.md5("|".join(sorted(requirements)).encode()).hexdigest()[:12]
    tag = f"sandbox-dep-{key}"

    # 2. 先查有没有
    if image_exists(tag):
        return tag          # ★ 命中!零 build 成本

    # 3. 没有才真 build
    build_image(requirements, tag)
    return tag

效果:

请求 依赖清单 结果
第 1 次 numpy, pandas build → sandbox-dep-a1b2c3
第 2 次 numpy, pandas 哈希相同 → 秒复用
第 3 次 pandas, numpy 排序后哈希相同 → 还是复用!
第 4 次 numpy, pandas, scipy 哈希不同 → 新 build

缓存的 key 不是"请求",而是"依赖集合"------相同依赖组合只 build 一次。

11.4 和 Maven 依赖缓存是同一个套路

Maven 动态 build 镜像
pom.xml 没变 → 用 ~/.m2 缓存 依赖清单没变 → 用已有镜像 tag
pom.xml 变了 → 重新下载 依赖清单变了 → 重新 build
依赖坐标算 key 依赖列表算哈希当 tag

11.5 Docker 分层缓存

复制代码
build 镜像 = 分层叠加
├─ 基础层 FROM python:3.11      ← 已存在,直接复用(不重建)
├─ 依赖层 RUN pip install xxx   ← ★ 只有这层是新的
└─ 结果:一个新镜像 tag

只要 pip install 那行没变,Docker 直接复用上次的层,秒过。

11.6 动态 build 完整流程

#mermaid-svg-gdmUCkHE6d3zS3bc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gdmUCkHE6d3zS3bc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gdmUCkHE6d3zS3bc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gdmUCkHE6d3zS3bc .error-icon{fill:#552222;}#mermaid-svg-gdmUCkHE6d3zS3bc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gdmUCkHE6d3zS3bc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gdmUCkHE6d3zS3bc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gdmUCkHE6d3zS3bc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gdmUCkHE6d3zS3bc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gdmUCkHE6d3zS3bc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gdmUCkHE6d3zS3bc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gdmUCkHE6d3zS3bc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gdmUCkHE6d3zS3bc .marker.cross{stroke:#333333;}#mermaid-svg-gdmUCkHE6d3zS3bc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gdmUCkHE6d3zS3bc p{margin:0;}#mermaid-svg-gdmUCkHE6d3zS3bc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gdmUCkHE6d3zS3bc .cluster-label text{fill:#333;}#mermaid-svg-gdmUCkHE6d3zS3bc .cluster-label span{color:#333;}#mermaid-svg-gdmUCkHE6d3zS3bc .cluster-label span p{background-color:transparent;}#mermaid-svg-gdmUCkHE6d3zS3bc .label text,#mermaid-svg-gdmUCkHE6d3zS3bc span{fill:#333;color:#333;}#mermaid-svg-gdmUCkHE6d3zS3bc .node rect,#mermaid-svg-gdmUCkHE6d3zS3bc .node circle,#mermaid-svg-gdmUCkHE6d3zS3bc .node ellipse,#mermaid-svg-gdmUCkHE6d3zS3bc .node polygon,#mermaid-svg-gdmUCkHE6d3zS3bc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gdmUCkHE6d3zS3bc .rough-node .label text,#mermaid-svg-gdmUCkHE6d3zS3bc .node .label text,#mermaid-svg-gdmUCkHE6d3zS3bc .image-shape .label,#mermaid-svg-gdmUCkHE6d3zS3bc .icon-shape .label{text-anchor:middle;}#mermaid-svg-gdmUCkHE6d3zS3bc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gdmUCkHE6d3zS3bc .rough-node .label,#mermaid-svg-gdmUCkHE6d3zS3bc .node .label,#mermaid-svg-gdmUCkHE6d3zS3bc .image-shape .label,#mermaid-svg-gdmUCkHE6d3zS3bc .icon-shape .label{text-align:center;}#mermaid-svg-gdmUCkHE6d3zS3bc .node.clickable{cursor:pointer;}#mermaid-svg-gdmUCkHE6d3zS3bc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gdmUCkHE6d3zS3bc .arrowheadPath{fill:#333333;}#mermaid-svg-gdmUCkHE6d3zS3bc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gdmUCkHE6d3zS3bc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gdmUCkHE6d3zS3bc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gdmUCkHE6d3zS3bc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gdmUCkHE6d3zS3bc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gdmUCkHE6d3zS3bc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gdmUCkHE6d3zS3bc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gdmUCkHE6d3zS3bc .cluster text{fill:#333;}#mermaid-svg-gdmUCkHE6d3zS3bc .cluster span{color:#333;}#mermaid-svg-gdmUCkHE6d3zS3bc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gdmUCkHE6d3zS3bc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gdmUCkHE6d3zS3bc rect.text{fill:none;stroke-width:0;}#mermaid-svg-gdmUCkHE6d3zS3bc .icon-shape,#mermaid-svg-gdmUCkHE6d3zS3bc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gdmUCkHE6d3zS3bc .icon-shape p,#mermaid-svg-gdmUCkHE6d3zS3bc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gdmUCkHE6d3zS3bc .icon-shape .label rect,#mermaid-svg-gdmUCkHE6d3zS3bc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gdmUCkHE6d3zS3bc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gdmUCkHE6d3zS3bc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gdmUCkHE6d3zS3bc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 有
没有
请求: code + requirements=numpy,pandas
① 排序算哈希 → tag=sandbox-dep-a1b2c3
② 仓库有这个 tag 吗?
③ 直接复用 ✅
④ build 新镜像
⑤ 推入仓库供下次复用
⑥ 用该镜像拉 Pod 跑代码

11.7 大全套 vs 动态 build

大全套镜像 动态 build
镜像体积 几 GB 按需,几百 MB
启动速度 慢 快
灵活度 低(总有漏) 高(要啥装啥)
存储成本 高 低
首次准备 一次搞定 首次略慢

主流是"预置常用 + 动态兜底 + LLM 自愈"三件套组合。


第十二部分:远程沙箱服务

12.1 需求:agent 不一定能拉起本地容器

现实里常有两种困境:

  1. agent 自己跑在容器里,不想挂 docker.sock(危险)
  2. agent 所在机器没有 Docker / 没有权限

解法:把"拉起容器"这件事,从 agent 本机搬到一个专用服务上------本地另一台机器、或内网的一台"私有沙箱服务器"。

复制代码
┌──────────────┐        网络        ┌──────────────────┐
│  agent 程序   │ ─────HTTP/gRPC───► │  沙箱服务器(你的)  │
│  (哪都行)     │ ◄────返回结果───── │  跑 Docker 容器   │
└──────────────┘                    └──────────────────┘

这在架构上叫"远程代码执行服务"(Remote Code Execution Service)------云端 E2B 那套逻辑的私有化版本。

12.2 为什么值得这么做

动机 说明
安全解耦 agent 本身不需要 Docker 权限,宿主更干净
资源集中 沙箱机器统一配好、装好依赖、集中管理
规避 DinD 直接绕开"容器里拉容器"的难题

特别是第三条 ------remote sandbox 是"agent 跑在容器里"这一困境的最优雅解法:agent 容器只需发 HTTPS 请求,不需要任何本地容器运行时权限。

12.3 实现方案(从简到繁)

方案 1:最简------HTTP 接口 + Docker SDK

python 复制代码
# ===== 沙箱服务器 sandbox_server.py =====
from fastapi import FastAPI
from pydantic import BaseModel
import docker

app = FastAPI()
client = docker.from_env()

class ExecRequest(BaseModel):
    code: str
    language: str = "python"
    requirements: list[str] = []
    timeout: int = 30

@app.post("/execute")
def execute(req: ExecRequest):
    container = client.containers.run(
        "my-sandbox:latest",
        command=["python", "-c", req.code],
        network_disabled=True, mem_limit="256m",
        cpu_quota=50000, read_only=True,
        remove=True, detach=False,
    )
    return {"output": container.decode("utf-8")}
python 复制代码
# ===== agent 侧 =====
import requests

def run_code_remote(code):
    r = requests.post("http://sandbox-host:8000/execute",
                      json={"code": code, "language": "python"}, timeout=60)
    return r.json()["output"]

本质就是把"本地 containers.run()"换成"发个 HTTP 请求,让另一台机器 containers.run()"。

方案 2:异步 + 任务队列(生产级)

同步 HTTP 有超时限制,长任务会断。生产做法用队列:
Docker 沙箱Worker 消息队列(Redis/RabbitMQ) agent程序 Docker 沙箱Worker 消息队列(Redis/RabbitMQ) agent程序 #mermaid-svg-R68Xyeg0y0MOXWT9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-R68Xyeg0y0MOXWT9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-R68Xyeg0y0MOXWT9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-R68Xyeg0y0MOXWT9 .error-icon{fill:#552222;}#mermaid-svg-R68Xyeg0y0MOXWT9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-R68Xyeg0y0MOXWT9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-R68Xyeg0y0MOXWT9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-R68Xyeg0y0MOXWT9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-R68Xyeg0y0MOXWT9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-R68Xyeg0y0MOXWT9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-R68Xyeg0y0MOXWT9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-R68Xyeg0y0MOXWT9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-R68Xyeg0y0MOXWT9 .marker.cross{stroke:#333333;}#mermaid-svg-R68Xyeg0y0MOXWT9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-R68Xyeg0y0MOXWT9 p{margin:0;}#mermaid-svg-R68Xyeg0y0MOXWT9 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-R68Xyeg0y0MOXWT9 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-R68Xyeg0y0MOXWT9 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-R68Xyeg0y0MOXWT9 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-R68Xyeg0y0MOXWT9 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-R68Xyeg0y0MOXWT9 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-R68Xyeg0y0MOXWT9 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-R68Xyeg0y0MOXWT9 .sequenceNumber{fill:white;}#mermaid-svg-R68Xyeg0y0MOXWT9 #sequencenumber{fill:#333;}#mermaid-svg-R68Xyeg0y0MOXWT9 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-R68Xyeg0y0MOXWT9 .messageText{fill:#333;stroke:none;}#mermaid-svg-R68Xyeg0y0MOXWT9 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-R68Xyeg0y0MOXWT9 .labelText,#mermaid-svg-R68Xyeg0y0MOXWT9 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-R68Xyeg0y0MOXWT9 .loopText,#mermaid-svg-R68Xyeg0y0MOXWT9 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-R68Xyeg0y0MOXWT9 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-R68Xyeg0y0MOXWT9 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-R68Xyeg0y0MOXWT9 .noteText,#mermaid-svg-R68Xyeg0y0MOXWT9 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-R68Xyeg0y0MOXWT9 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-R68Xyeg0y0MOXWT9 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-R68Xyeg0y0MOXWT9 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-R68Xyeg0y0MOXWT9 .actorPopupMenu{position:absolute;}#mermaid-svg-R68Xyeg0y0MOXWT9 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-R68Xyeg0y0MOXWT9 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-R68Xyeg0y0MOXWT9 .actor-man circle,#mermaid-svg-R68Xyeg0y0MOXWT9 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-R68Xyeg0y0MOXWT9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 提交任务(code, deps)分发任务拉容器执行结果写回结果轮询/订阅结果返回结果

好处:解耦、可并发、可重试、支持长任务。Celery / RQ / 自建均可。

方案 3:★ 容器池------解决冷启动

每次新建容器慢(几百毫秒~秒级)。改进:在沙箱服务器上预热一批容器待命。

python 复制代码
class ContainerPool:
    def __init__(self, size=5, image="my-sandbox:latest"):
        self.image = image
        self.pool = [self._spawn() for _ in range(size)]

    def _spawn(self):
        return client.containers.create(     # 只创建,不启动
            self.image, command=["sleep", "infinity"],
            network_disabled=True, mem_limit="256m",
        )

    def acquire(self):
        return self.pool.pop() if self.pool else self._spawn()

    def release(self, c):
        c.remove(force=True)                 # ★ 用完销毁,不复用同一容器
        self.pool.append(self._spawn())      # 补一个新的

关键:池化的是"启动开销",不是"运行状态"。用完的容器必须销毁重建------不同用户的代码不能共享同一容器。

方案 4:远程 K8s------最彻底的解耦

沙箱服务器本身是 K8s 集群时,agent 只需调 K8s API 起沙箱 Pod,连 Docker 都不用碰。

12.4 ★ 生产级设计要点

远程沙箱不只是"发个 HTTP",真功夫在这些:

要点 做法
认证 agent 与沙箱服务器之间必须有 token/mTLS,否则谁都能调你的沙箱跑挖矿
隔离 沙箱服务器部署在内网 ,不对外暴露
依赖 服务器上预置好镜像(见第十一部分)
超时 服务端强制超时杀容器
输出限制 限制返回大小,防撑爆 agent
并发控制 限制同时运行的容器数,防打满机器
清理 定时清理残留容器
文件传输 设计"传文件进/取文件出"接口(见第十三部分)

⚠️ 别把沙箱服务暴露到公网。它是"能跑任意代码"的服务,裸奔 = 给别人一台肉鸡。

12.5 完整架构图

#mermaid-svg-CQCrpKlVkTzCkan3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CQCrpKlVkTzCkan3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CQCrpKlVkTzCkan3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CQCrpKlVkTzCkan3 .error-icon{fill:#552222;}#mermaid-svg-CQCrpKlVkTzCkan3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CQCrpKlVkTzCkan3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CQCrpKlVkTzCkan3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CQCrpKlVkTzCkan3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CQCrpKlVkTzCkan3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CQCrpKlVkTzCkan3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CQCrpKlVkTzCkan3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CQCrpKlVkTzCkan3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CQCrpKlVkTzCkan3 .marker.cross{stroke:#333333;}#mermaid-svg-CQCrpKlVkTzCkan3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CQCrpKlVkTzCkan3 p{margin:0;}#mermaid-svg-CQCrpKlVkTzCkan3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CQCrpKlVkTzCkan3 .cluster-label text{fill:#333;}#mermaid-svg-CQCrpKlVkTzCkan3 .cluster-label span{color:#333;}#mermaid-svg-CQCrpKlVkTzCkan3 .cluster-label span p{background-color:transparent;}#mermaid-svg-CQCrpKlVkTzCkan3 .label text,#mermaid-svg-CQCrpKlVkTzCkan3 span{fill:#333;color:#333;}#mermaid-svg-CQCrpKlVkTzCkan3 .node rect,#mermaid-svg-CQCrpKlVkTzCkan3 .node circle,#mermaid-svg-CQCrpKlVkTzCkan3 .node ellipse,#mermaid-svg-CQCrpKlVkTzCkan3 .node polygon,#mermaid-svg-CQCrpKlVkTzCkan3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CQCrpKlVkTzCkan3 .rough-node .label text,#mermaid-svg-CQCrpKlVkTzCkan3 .node .label text,#mermaid-svg-CQCrpKlVkTzCkan3 .image-shape .label,#mermaid-svg-CQCrpKlVkTzCkan3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-CQCrpKlVkTzCkan3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CQCrpKlVkTzCkan3 .rough-node .label,#mermaid-svg-CQCrpKlVkTzCkan3 .node .label,#mermaid-svg-CQCrpKlVkTzCkan3 .image-shape .label,#mermaid-svg-CQCrpKlVkTzCkan3 .icon-shape .label{text-align:center;}#mermaid-svg-CQCrpKlVkTzCkan3 .node.clickable{cursor:pointer;}#mermaid-svg-CQCrpKlVkTzCkan3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CQCrpKlVkTzCkan3 .arrowheadPath{fill:#333333;}#mermaid-svg-CQCrpKlVkTzCkan3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CQCrpKlVkTzCkan3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CQCrpKlVkTzCkan3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CQCrpKlVkTzCkan3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CQCrpKlVkTzCkan3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CQCrpKlVkTzCkan3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CQCrpKlVkTzCkan3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CQCrpKlVkTzCkan3 .cluster text{fill:#333;}#mermaid-svg-CQCrpKlVkTzCkan3 .cluster span{color:#333;}#mermaid-svg-CQCrpKlVkTzCkan3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CQCrpKlVkTzCkan3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CQCrpKlVkTzCkan3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-CQCrpKlVkTzCkan3 .icon-shape,#mermaid-svg-CQCrpKlVkTzCkan3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CQCrpKlVkTzCkan3 .icon-shape p,#mermaid-svg-CQCrpKlVkTzCkan3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CQCrpKlVkTzCkan3 .icon-shape .label rect,#mermaid-svg-CQCrpKlVkTzCkan3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CQCrpKlVkTzCkan3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CQCrpKlVkTzCkan3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CQCrpKlVkTzCkan3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 沙箱服务器(内网)
Agent 所在机器
① 提交代码
④ 返回结果
② 借出容器执行
③ 结果
Docker 运行时
沙箱容器1

断网/限资源
沙箱容器2
沙箱容器3
agent 程序
沙箱客户端

(HTTP/gRPC SDK)
沙箱 API 服务

(FastAPI/认证)
容器池

(预热待命)

本质上,你等于把"云端 agent 平台"的沙箱服务,自己私有化部署了一台。


第十三部分:产物回传------沙箱产生的文件怎么带回

13.1 先给产物分类(别一上来就想"传文件")

产物类型 例子 大小 传输方式
① 标准输出 print、日志 小 stdout 直接带回
② 返回值 函数 return 小 约定输出通道
③ 生成的文件 CSV、图片、Excel 中~大 ★ 需要文件传输机制
④ 目录结构 一堆文件、多级目录 大 打包/挂载
⑤ 副作用 改了数据库、发请求 --- 不传,直接生效

①② 是"文本结果",⑤ 是"世界已变",只有 ③④ 才真正需要"传回"。

13.2 ★ 两种"传回"范式(记住它俩,后面都是变体)

范式 A:拉取式(Pull)------产物落在共享卷,agent 去取

复制代码
沙箱容器 → 写到挂载目录 → agent 读该目录

范式 B:推送式(Push)------沙箱主动把产物发出去

复制代码
沙箱容器 → 调上传接口 → 传到对象存储/回调地址
拉取式 (Pull) 推送式 (Push)
方向 agent 主动去取 沙箱主动送出
依赖 共享存储 网络回调
适合 agent 和沙箱能共享文件系统 agent 和沙箱只能靠网络
典型 K8s 挂 PVC / volume 上传到 S3 / 回调 webhook

13.3 方案全景

方案 1:共享卷挂载(拉取式)------ 同机/集群内首选

python 复制代码
client.containers.run(
    "my-sandbox:latest",
    command=["python", "-c", code],
    volumes={"/host/sandbox_out": {"bind": "/output", "mode": "rw"}},
    #          ↑ 宿主目录                          ↑ 容器内目录
)
# 约定: 代码把产物写到 /output;跑完 agent 读宿主对应目录

K8s 场景用 PVC 共享卷同理。

优点 :简单、快、不传大文件。

缺点 :要求 agent 和沙箱能共享文件系统(同机 or 同集群)。

方案 2:docker cp(拉取式)------ 本地 Docker 专用

python 复制代码
container = client.containers.run("my-sandbox:latest",
                                  command=["python", "-c", code],
                                  detach=True)   # ★ 后台跑,不自动删
container.wait()
subprocess.run(["docker", "cp", f"{container.id}:/output", "./local_out"])
container.remove()

注意:必须先别删容器。只适合本地 Docker。

方案 3:对象存储上传(推送式)------ 分布式/远程首选

沙箱跑完,代码自己把产物上传到对象存储(S3/MinIO),agent 从对象存储下载。

优点 :跨网络、跨机房都能用,天然适合分布式。

缺点 :沙箱需要网络出口------破坏"默认断网"的隔离 ,必须做白名单(只放行对象存储)。

方案 4:webhook 回调(推送式)------ 长任务/异步首选

沙箱跑完主动回调 agent 的一个地址,适合异步长任务。

方案 5:base64 内联(推送式)------ 小文件专用

产物很小(几十 KB 内),直接 base64 编码塞进 stdout。

优点 :零基础设施。

缺点:只适合小文件(大文件撑爆内存)。

13.4 选型决策表

场景 推荐方案
只返回文本结果 直接 stdout
本地 Docker + 有产物 共享卷挂载 或 docker cp
K8s 集群内 PVC 共享卷
沙箱和 agent 不在同一存储域 对象存储
异步长任务 webhook 回调
产物很小 base64 内联

13.5 ★ 实战要点(翻车重灾区)

要点 说明
约定输出目录 统一约定"产物放 /output",别让代码随便写
大小限制 必须限产物大小,否则一个 10GB 文件传死你
清理 传完要清理沙箱内临时产物,防泄漏给下一个人
命名冲突 多任务并发时用 task_id 隔离目录
断网悖论 推送式要联网,和"默认断网"冲突------做白名单出口
格式约定 输出"结构化清单"(JSON 列出产物路径+类型),别让 agent 猜
异步适配 长任务别用同步 HTTP 等,会超时断开

13.6 ★ 推荐的"产物回传协议"

正规做法是约定一套产物协议,不靠猜:

json 复制代码
{
  "status": "success",
  "stdout": "...",
  "stderr": "",
  "artifacts": [
    {"name": "result.csv", "type": "file",  "size": 2048,  "path": "task_123/result.csv"},
    {"name": "chart.png",  "type": "image", "size": 51200, "path": "task_123/chart.png"}
  ],
  "artifacts_uri": {
    "type": "s3", "bucket": "sandbox-output", "prefix": "task_123/"
  }
}

agent 拿到结构就知道:有哪些产物、多大、去哪取。

13.7 一张图总收口

#mermaid-svg-I8jjW2NdzSpcgT33{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-I8jjW2NdzSpcgT33 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-I8jjW2NdzSpcgT33 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-I8jjW2NdzSpcgT33 .error-icon{fill:#552222;}#mermaid-svg-I8jjW2NdzSpcgT33 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-I8jjW2NdzSpcgT33 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-I8jjW2NdzSpcgT33 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-I8jjW2NdzSpcgT33 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-I8jjW2NdzSpcgT33 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-I8jjW2NdzSpcgT33 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-I8jjW2NdzSpcgT33 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-I8jjW2NdzSpcgT33 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-I8jjW2NdzSpcgT33 .marker.cross{stroke:#333333;}#mermaid-svg-I8jjW2NdzSpcgT33 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-I8jjW2NdzSpcgT33 p{margin:0;}#mermaid-svg-I8jjW2NdzSpcgT33 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-I8jjW2NdzSpcgT33 .cluster-label text{fill:#333;}#mermaid-svg-I8jjW2NdzSpcgT33 .cluster-label span{color:#333;}#mermaid-svg-I8jjW2NdzSpcgT33 .cluster-label span p{background-color:transparent;}#mermaid-svg-I8jjW2NdzSpcgT33 .label text,#mermaid-svg-I8jjW2NdzSpcgT33 span{fill:#333;color:#333;}#mermaid-svg-I8jjW2NdzSpcgT33 .node rect,#mermaid-svg-I8jjW2NdzSpcgT33 .node circle,#mermaid-svg-I8jjW2NdzSpcgT33 .node ellipse,#mermaid-svg-I8jjW2NdzSpcgT33 .node polygon,#mermaid-svg-I8jjW2NdzSpcgT33 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-I8jjW2NdzSpcgT33 .rough-node .label text,#mermaid-svg-I8jjW2NdzSpcgT33 .node .label text,#mermaid-svg-I8jjW2NdzSpcgT33 .image-shape .label,#mermaid-svg-I8jjW2NdzSpcgT33 .icon-shape .label{text-anchor:middle;}#mermaid-svg-I8jjW2NdzSpcgT33 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-I8jjW2NdzSpcgT33 .rough-node .label,#mermaid-svg-I8jjW2NdzSpcgT33 .node .label,#mermaid-svg-I8jjW2NdzSpcgT33 .image-shape .label,#mermaid-svg-I8jjW2NdzSpcgT33 .icon-shape .label{text-align:center;}#mermaid-svg-I8jjW2NdzSpcgT33 .node.clickable{cursor:pointer;}#mermaid-svg-I8jjW2NdzSpcgT33 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-I8jjW2NdzSpcgT33 .arrowheadPath{fill:#333333;}#mermaid-svg-I8jjW2NdzSpcgT33 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-I8jjW2NdzSpcgT33 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-I8jjW2NdzSpcgT33 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-I8jjW2NdzSpcgT33 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-I8jjW2NdzSpcgT33 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-I8jjW2NdzSpcgT33 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-I8jjW2NdzSpcgT33 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-I8jjW2NdzSpcgT33 .cluster text{fill:#333;}#mermaid-svg-I8jjW2NdzSpcgT33 .cluster span{color:#333;}#mermaid-svg-I8jjW2NdzSpcgT33 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-I8jjW2NdzSpcgT33 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-I8jjW2NdzSpcgT33 rect.text{fill:none;stroke-width:0;}#mermaid-svg-I8jjW2NdzSpcgT33 .icon-shape,#mermaid-svg-I8jjW2NdzSpcgT33 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-I8jjW2NdzSpcgT33 .icon-shape p,#mermaid-svg-I8jjW2NdzSpcgT33 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-I8jjW2NdzSpcgT33 .icon-shape .label rect,#mermaid-svg-I8jjW2NdzSpcgT33 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-I8jjW2NdzSpcgT33 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-I8jjW2NdzSpcgT33 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-I8jjW2NdzSpcgT33 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} agent
沙箱容器
共享卷挂载
S3/MinIO
HTTP 回调
跑代码
产物写到 /output
或: 上传到对象存储
读共享卷(拉取)
从对象存储下载(拉取)
接收 webhook(推送)


第十四部分:沙箱执行框架

14.1 什么是"沙箱执行框架"

沙箱执行框架 = 一层"包在用户代码外面"的固定逻辑,负责处理所有"用户代码不该关心、但必须做"的事。

它管的不只是上传,而是用户代码的整个生命周期:

框架负责的杂务 说明
准备环境 放好输入文件、切到工作目录
跑用户代码 真正 exec 用户脚本
捕获输出 收集 stdout/stderr/返回值
收集产物 扫描 /output 目录
上传产物 传到 MinIO/S3(第十三部分)
限时限资源 超时杀、限内存
清理 销毁容器、清临时文件
组装结果 打包成结构化 JSON 返回

14.2 用户代码 vs 框架代码

#mermaid-svg-Po2T3vewzW515uRh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Po2T3vewzW515uRh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Po2T3vewzW515uRh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Po2T3vewzW515uRh .error-icon{fill:#552222;}#mermaid-svg-Po2T3vewzW515uRh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Po2T3vewzW515uRh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Po2T3vewzW515uRh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Po2T3vewzW515uRh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Po2T3vewzW515uRh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Po2T3vewzW515uRh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Po2T3vewzW515uRh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Po2T3vewzW515uRh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Po2T3vewzW515uRh .marker.cross{stroke:#333333;}#mermaid-svg-Po2T3vewzW515uRh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Po2T3vewzW515uRh p{margin:0;}#mermaid-svg-Po2T3vewzW515uRh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Po2T3vewzW515uRh .cluster-label text{fill:#333;}#mermaid-svg-Po2T3vewzW515uRh .cluster-label span{color:#333;}#mermaid-svg-Po2T3vewzW515uRh .cluster-label span p{background-color:transparent;}#mermaid-svg-Po2T3vewzW515uRh .label text,#mermaid-svg-Po2T3vewzW515uRh span{fill:#333;color:#333;}#mermaid-svg-Po2T3vewzW515uRh .node rect,#mermaid-svg-Po2T3vewzW515uRh .node circle,#mermaid-svg-Po2T3vewzW515uRh .node ellipse,#mermaid-svg-Po2T3vewzW515uRh .node polygon,#mermaid-svg-Po2T3vewzW515uRh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Po2T3vewzW515uRh .rough-node .label text,#mermaid-svg-Po2T3vewzW515uRh .node .label text,#mermaid-svg-Po2T3vewzW515uRh .image-shape .label,#mermaid-svg-Po2T3vewzW515uRh .icon-shape .label{text-anchor:middle;}#mermaid-svg-Po2T3vewzW515uRh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Po2T3vewzW515uRh .rough-node .label,#mermaid-svg-Po2T3vewzW515uRh .node .label,#mermaid-svg-Po2T3vewzW515uRh .image-shape .label,#mermaid-svg-Po2T3vewzW515uRh .icon-shape .label{text-align:center;}#mermaid-svg-Po2T3vewzW515uRh .node.clickable{cursor:pointer;}#mermaid-svg-Po2T3vewzW515uRh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Po2T3vewzW515uRh .arrowheadPath{fill:#333333;}#mermaid-svg-Po2T3vewzW515uRh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Po2T3vewzW515uRh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Po2T3vewzW515uRh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Po2T3vewzW515uRh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Po2T3vewzW515uRh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Po2T3vewzW515uRh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Po2T3vewzW515uRh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Po2T3vewzW515uRh .cluster text{fill:#333;}#mermaid-svg-Po2T3vewzW515uRh .cluster span{color:#333;}#mermaid-svg-Po2T3vewzW515uRh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Po2T3vewzW515uRh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Po2T3vewzW515uRh rect.text{fill:none;stroke-width:0;}#mermaid-svg-Po2T3vewzW515uRh .icon-shape,#mermaid-svg-Po2T3vewzW515uRh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Po2T3vewzW515uRh .icon-shape p,#mermaid-svg-Po2T3vewzW515uRh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Po2T3vewzW515uRh .icon-shape .label rect,#mermaid-svg-Po2T3vewzW515uRh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Po2T3vewzW515uRh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Po2T3vewzW515uRh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Po2T3vewzW515uRh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 沙箱执行框架(固定不变)
用户代码(每次不同、不可信)
import pandas...

df.to_csv('/output/x.csv')
2. 捕获 stdout/stderr
3. 扫描 /output
4. 上传 MinIO
5. 组装结构化结果
6. 清理

  1. 准备: 放输入、切目录

框架是不变的"壳",用户代码是每次变的"芯"。

14.3 ★ 框架写在哪儿------它其实是"跨层"的

准确答案:框架不是"一个文件",而是一套分散在多层的逻辑。

落点 具体位置 形态
① 镜像内的脚本 镜像里 /framework/runner.py 最常见
② 镜像的 ENTRYPOINT/CMD Dockerfile 指定 最"原生"
③ 独立的 sidecar 容器 K8s 里另一个容器 最解耦
④ 沙箱服务端(容器外) 沙箱服务器的编排代码里 服务侧

#mermaid-svg-3mwTbsb5io97B9Qj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3mwTbsb5io97B9Qj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3mwTbsb5io97B9Qj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3mwTbsb5io97B9Qj .error-icon{fill:#552222;}#mermaid-svg-3mwTbsb5io97B9Qj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3mwTbsb5io97B9Qj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3mwTbsb5io97B9Qj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3mwTbsb5io97B9Qj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3mwTbsb5io97B9Qj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3mwTbsb5io97B9Qj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3mwTbsb5io97B9Qj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3mwTbsb5io97B9Qj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3mwTbsb5io97B9Qj .marker.cross{stroke:#333333;}#mermaid-svg-3mwTbsb5io97B9Qj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3mwTbsb5io97B9Qj p{margin:0;}#mermaid-svg-3mwTbsb5io97B9Qj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3mwTbsb5io97B9Qj .cluster-label text{fill:#333;}#mermaid-svg-3mwTbsb5io97B9Qj .cluster-label span{color:#333;}#mermaid-svg-3mwTbsb5io97B9Qj .cluster-label span p{background-color:transparent;}#mermaid-svg-3mwTbsb5io97B9Qj .label text,#mermaid-svg-3mwTbsb5io97B9Qj span{fill:#333;color:#333;}#mermaid-svg-3mwTbsb5io97B9Qj .node rect,#mermaid-svg-3mwTbsb5io97B9Qj .node circle,#mermaid-svg-3mwTbsb5io97B9Qj .node ellipse,#mermaid-svg-3mwTbsb5io97B9Qj .node polygon,#mermaid-svg-3mwTbsb5io97B9Qj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3mwTbsb5io97B9Qj .rough-node .label text,#mermaid-svg-3mwTbsb5io97B9Qj .node .label text,#mermaid-svg-3mwTbsb5io97B9Qj .image-shape .label,#mermaid-svg-3mwTbsb5io97B9Qj .icon-shape .label{text-anchor:middle;}#mermaid-svg-3mwTbsb5io97B9Qj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3mwTbsb5io97B9Qj .rough-node .label,#mermaid-svg-3mwTbsb5io97B9Qj .node .label,#mermaid-svg-3mwTbsb5io97B9Qj .image-shape .label,#mermaid-svg-3mwTbsb5io97B9Qj .icon-shape .label{text-align:center;}#mermaid-svg-3mwTbsb5io97B9Qj .node.clickable{cursor:pointer;}#mermaid-svg-3mwTbsb5io97B9Qj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3mwTbsb5io97B9Qj .arrowheadPath{fill:#333333;}#mermaid-svg-3mwTbsb5io97B9Qj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3mwTbsb5io97B9Qj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3mwTbsb5io97B9Qj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3mwTbsb5io97B9Qj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3mwTbsb5io97B9Qj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3mwTbsb5io97B9Qj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3mwTbsb5io97B9Qj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3mwTbsb5io97B9Qj .cluster text{fill:#333;}#mermaid-svg-3mwTbsb5io97B9Qj .cluster span{color:#333;}#mermaid-svg-3mwTbsb5io97B9Qj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3mwTbsb5io97B9Qj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3mwTbsb5io97B9Qj rect.text{fill:none;stroke-width:0;}#mermaid-svg-3mwTbsb5io97B9Qj .icon-shape,#mermaid-svg-3mwTbsb5io97B9Qj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3mwTbsb5io97B9Qj .icon-shape p,#mermaid-svg-3mwTbsb5io97B9Qj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3mwTbsb5io97B9Qj .icon-shape .label rect,#mermaid-svg-3mwTbsb5io97B9Qj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3mwTbsb5io97B9Qj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3mwTbsb5io97B9Qj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3mwTbsb5io97B9Qj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} sidecar(另一个容器)
上传器

让凭证远离不可信代码
沙箱镜像内
runner.py

准备/跑/捕获/收集
沙箱服务端(容器外)
起容器 / 传参 / 收结果 / 清理

14.4 形态①:镜像内的 runner 脚本(最直观)

dockerfile 复制代码
# Dockerfile.sandbox
FROM python:3.11-slim
RUN pip install pandas boto3

# ★ 框架代码打进镜像
COPY framework/runner.py /framework/runner.py

# ★ 镜像的默认入口就是框架
ENTRYPOINT ["python", "/framework/runner.py"]
python 复制代码
# /framework/runner.py ------ 镜像里,用户看不到也改不了
import subprocess, os, json, glob
import boto3

def main():
    code_path = os.environ["USER_CODE_PATH"]   # 用户代码路径,由沙箱服务传入
    os.makedirs("/output", exist_ok=True)      # ① 准备

    proc = subprocess.run(                     # ② 跑用户代码
        ["python", code_path], capture_output=True,
        text=True, timeout=30, cwd="/work",
    )

    artifacts = []                             # ③ 收集 + ④ 上传
    for f in glob.glob("/output/**/*", recursive=True):
        if os.path.isfile(f):
            key = f"{os.environ['TASK_ID']}/{os.path.basename(f)}"
            upload_to_minio(f, key)
            artifacts.append({"name": os.path.basename(f), "path": key})

    print(json.dumps({                         # ⑤ 组装结果
        "status": "ok" if proc.returncode == 0 else "error",
        "stdout": proc.stdout, "stderr": proc.stderr, "artifacts": artifacts,
    }))

main()

沙箱服务这样起容器:

python 复制代码
client.containers.run(
    "my-sandbox:latest",
    environment={
        "USER_CODE_PATH": "/code/user.py",
        "TASK_ID": task_id,
        "S3_ENDPOINT": "http://minio:9000",
        "S3_KEY": "...", "S3_SECRET": "...",
    },
    volumes={"/host/codes": {"bind": "/code", "mode": "ro"}},  # 用户代码挂进来
)

分工清晰:镜像里有框架(runner.py);沙箱服务负责挂代码、传环境变量、起容器、收结果。

14.5 形态③:sidecar(框架在另一个容器里)

框架逻辑不放进沙箱镜像,而是放在旁边的 sidecar:

yaml 复制代码
containers:
  - name: runner          # 只跑用户代码,镜像可极简
    image: my-sandbox:latest
    volumeMounts:
      - { name: output, mountPath: /output }
  - name: uploader        # ★ 框架的一部分在这
    image: minio/mc:latest
    command: ["sh", "-c", "while true; do mc mirror /output/ myminio/sandbox-output/$TASK_ID/; sleep 2; done"]
    volumeMounts:
      - { name: output, mountPath: /output }   # ★ 共享同一个卷
volumes:
  - name: output
    emptyDir: {}

sidecar 模式最安全------跑用户代码的主容器根本没有任何 S3 凭证。

14.6 ★ 为什么上传逻辑绝不能写在用户脚本里

假设让用户/模型生成的脚本自己调 s3.upload():

问题 说明
污染业务代码 用户只想算数据,却要写一堆上传样板
模型容易写错 LLM 每次生成 S3 上传代码,参数/凭证易错
凭证泄露 脚本里得塞 access key------等于把凭证给了不可信代码
依赖耦合 脚本得装 boto3,还得知道 bucket/endpoint
无法统一管控 大小限制、路径规范、清理策略全乱
破坏断网隔离 每个脚本都要开网络出口

核心矛盾:脚本是"不可信、临时、不该知道基础设施"的,你却要它操作基础设施------角色错配。

比方:让访客自己拿钥匙去开你家保险柜存东西。 正确做法是在门口装个收纳箱。

14.7 三种模式对照表

脚本里写 wrapper 层 sidecar
用户代码是否干净 ❌ 污染 ✅ 干净 ✅ 干净
凭证暴露给用户代码 ❌ 暴露 ⚠️ 环境变量可见 ✅✅ 完全不可见
换传输方式成本 改所有脚本 改一处 改 sidecar
实现复杂度 低 中 中高
适合 ❌ 不推荐 单容器场景 K8s 场景

14.8 ★ 怎么决定"某段逻辑放哪层"

逻辑 放哪层 为什么
起容器、传参、销毁 服务端 只有"容器外"才能做
跑用户代码、捕获 stdout 镜像内 必须和用户代码同进程树
扫 /output、打包 镜像内 或 服务端 看能不能共享卷
上传(需要凭证) sidecar 优先 ★ 让凭证远离不可信代码
组装返回结构 服务端 面向 agent 的接口

核心原则:

  • "必须和用户代码同环境"的 → 放镜像里
  • "不该让用户代码碰到"的(尤其凭证)→ 放 sidecar 或服务端
  • "面向外部接口"的 → 放服务端

14.9 为什么框架要"独立于用户代码"(设计哲学)

如果不用框架 用了框架
用户代码得自己管上传、清理 用户代码只管业务
凭证得给用户代码 凭证被隔离
每次生成代码都要写样板 业务代码干净
无法统一限时/限资源 框架统一管控
产物格式五花八门 框架统一协议

框架的价值 = 把"基础设施杂务"从"业务逻辑"里剥离。用户代码只该关心"算数",不该关心"怎么上传"。

这就是软件工程里最经典的关注点分离------只不过这次分离的是"不可信业务代码"和"可信基础设施逻辑"。

14.10 一句话收口

沙箱执行框架 = 包在用户代码外的一层"杂务处理逻辑"(准备/跑/捕获/上传/清理/组装)。它不写在用户脚本里,而是写在"镜像内 runner" + "sidecar 上传器" + "沙箱服务端编排"这三层中的某几层。原则:必须同环境的放镜像里,不该让用户碰的(凭证)放 sidecar/服务端。


第十五部分:Agent 实现方案的共识与分歧

15.1 一个诚实的判断

走到这里,你可能想问:实现一个 agent 要考虑这么多东西,那业界有没有一套"最佳实现方案"?

答案是:没有"唯一最佳方案"。有共识的分层骨架,但没有标准答案;主流是"骨架趋同 + 细节各自摸索"。

换句话说:"用什么架构"基本达成共识了,"每个模块怎么填"还在百花齐放。

15.2 为什么没有统一方案(四个根本原因)

原因 说明
场景差异太大 写代码的 agent、做客服的、跑数据分析的,需求南辕北辙
技术演进太快 2023 年的最佳实践,2025 年可能已被推翻
没有"编译期"约束 传统软件错了编译不过;agent 的"对错"要跑起来才知道
概率性系统 ★ agent 不是确定性系统------没有确定性,就难有"最佳"

第四条最根本:传统软件的正确性是可证明 的,agent 的行为是概率分布。这从根本上决定了它难以形成硬标准。

15.3 ★ 共识的部分:五层骨架已趋同

虽然没有统一实现,但大家的分层结构已经高度一致:
#mermaid-svg-8V3QBG1dh43e42jN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8V3QBG1dh43e42jN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8V3QBG1dh43e42jN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8V3QBG1dh43e42jN .error-icon{fill:#552222;}#mermaid-svg-8V3QBG1dh43e42jN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8V3QBG1dh43e42jN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8V3QBG1dh43e42jN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8V3QBG1dh43e42jN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8V3QBG1dh43e42jN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8V3QBG1dh43e42jN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8V3QBG1dh43e42jN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8V3QBG1dh43e42jN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8V3QBG1dh43e42jN .marker.cross{stroke:#333333;}#mermaid-svg-8V3QBG1dh43e42jN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8V3QBG1dh43e42jN p{margin:0;}#mermaid-svg-8V3QBG1dh43e42jN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8V3QBG1dh43e42jN .cluster-label text{fill:#333;}#mermaid-svg-8V3QBG1dh43e42jN .cluster-label span{color:#333;}#mermaid-svg-8V3QBG1dh43e42jN .cluster-label span p{background-color:transparent;}#mermaid-svg-8V3QBG1dh43e42jN .label text,#mermaid-svg-8V3QBG1dh43e42jN span{fill:#333;color:#333;}#mermaid-svg-8V3QBG1dh43e42jN .node rect,#mermaid-svg-8V3QBG1dh43e42jN .node circle,#mermaid-svg-8V3QBG1dh43e42jN .node ellipse,#mermaid-svg-8V3QBG1dh43e42jN .node polygon,#mermaid-svg-8V3QBG1dh43e42jN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8V3QBG1dh43e42jN .rough-node .label text,#mermaid-svg-8V3QBG1dh43e42jN .node .label text,#mermaid-svg-8V3QBG1dh43e42jN .image-shape .label,#mermaid-svg-8V3QBG1dh43e42jN .icon-shape .label{text-anchor:middle;}#mermaid-svg-8V3QBG1dh43e42jN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8V3QBG1dh43e42jN .rough-node .label,#mermaid-svg-8V3QBG1dh43e42jN .node .label,#mermaid-svg-8V3QBG1dh43e42jN .image-shape .label,#mermaid-svg-8V3QBG1dh43e42jN .icon-shape .label{text-align:center;}#mermaid-svg-8V3QBG1dh43e42jN .node.clickable{cursor:pointer;}#mermaid-svg-8V3QBG1dh43e42jN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8V3QBG1dh43e42jN .arrowheadPath{fill:#333333;}#mermaid-svg-8V3QBG1dh43e42jN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8V3QBG1dh43e42jN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8V3QBG1dh43e42jN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8V3QBG1dh43e42jN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8V3QBG1dh43e42jN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8V3QBG1dh43e42jN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8V3QBG1dh43e42jN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8V3QBG1dh43e42jN .cluster text{fill:#333;}#mermaid-svg-8V3QBG1dh43e42jN .cluster span{color:#333;}#mermaid-svg-8V3QBG1dh43e42jN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8V3QBG1dh43e42jN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8V3QBG1dh43e42jN rect.text{fill:none;stroke-width:0;}#mermaid-svg-8V3QBG1dh43e42jN .icon-shape,#mermaid-svg-8V3QBG1dh43e42jN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8V3QBG1dh43e42jN .icon-shape p,#mermaid-svg-8V3QBG1dh43e42jN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8V3QBG1dh43e42jN .icon-shape .label rect,#mermaid-svg-8V3QBG1dh43e42jN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8V3QBG1dh43e42jN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8V3QBG1dh43e42jN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8V3QBG1dh43e42jN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ① 模型层

LLM 推理
② 编排层

循环/规划/状态机
③ 工具层

内置 + MCP
④ 记忆层

短期/长期
⑤ 执行层

沙箱

层 共识度 现状
模型层 ✅ 高 就是调 LLM API,没啥争议
编排层 ⚠️ 中 循环模式已共识,具体框架各不同
工具层 ✅ 高 JSON Schema + MCP 已是事实标准
记忆层 ❌ 低 最不成熟,各家玩法差异最大
执行层 ⚠️ 中 沙箱思路共识,实现选型分歧大

"五层骨架"是共识,"每层怎么填"是分歧。

15.4 真正趋同的那部分:编排循环(ReAct)

如果你只能记一个"共识",记这个:几乎所有 agent 都是同一个循环。
#mermaid-svg-g9U6Fq2ZrSjlL9gu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-g9U6Fq2ZrSjlL9gu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .error-icon{fill:#552222;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .marker.cross{stroke:#333333;}#mermaid-svg-g9U6Fq2ZrSjlL9gu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-g9U6Fq2ZrSjlL9gu p{margin:0;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .cluster-label text{fill:#333;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .cluster-label span{color:#333;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .cluster-label span p{background-color:transparent;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .label text,#mermaid-svg-g9U6Fq2ZrSjlL9gu span{fill:#333;color:#333;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .node rect,#mermaid-svg-g9U6Fq2ZrSjlL9gu .node circle,#mermaid-svg-g9U6Fq2ZrSjlL9gu .node ellipse,#mermaid-svg-g9U6Fq2ZrSjlL9gu .node polygon,#mermaid-svg-g9U6Fq2ZrSjlL9gu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .rough-node .label text,#mermaid-svg-g9U6Fq2ZrSjlL9gu .node .label text,#mermaid-svg-g9U6Fq2ZrSjlL9gu .image-shape .label,#mermaid-svg-g9U6Fq2ZrSjlL9gu .icon-shape .label{text-anchor:middle;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .rough-node .label,#mermaid-svg-g9U6Fq2ZrSjlL9gu .node .label,#mermaid-svg-g9U6Fq2ZrSjlL9gu .image-shape .label,#mermaid-svg-g9U6Fq2ZrSjlL9gu .icon-shape .label{text-align:center;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .node.clickable{cursor:pointer;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .arrowheadPath{fill:#333333;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-g9U6Fq2ZrSjlL9gu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-g9U6Fq2ZrSjlL9gu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-g9U6Fq2ZrSjlL9gu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .cluster text{fill:#333;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .cluster span{color:#333;}#mermaid-svg-g9U6Fq2ZrSjlL9gu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-g9U6Fq2ZrSjlL9gu rect.text{fill:none;stroke-width:0;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .icon-shape,#mermaid-svg-g9U6Fq2ZrSjlL9gu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .icon-shape p,#mermaid-svg-g9U6Fq2ZrSjlL9gu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .icon-shape .label rect,#mermaid-svg-g9U6Fq2ZrSjlL9gu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-g9U6Fq2ZrSjlL9gu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-g9U6Fq2ZrSjlL9gu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-g9U6Fq2ZrSjlL9gu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 要
不要
观察

Observation
思考

LLM 推理
要不要

调工具?
行动

执行工具
输出答案

这就是 ReAct 范式 (Reasoning + Acting)的精神内核。不管上层包装多花哨,底下都是这个 loop。

共识的是"循环",分歧的是:

分歧点 几种流派
规划方式 边想边做(ReAct) vs 先规划再做(Plan-and-Execute)
状态管理 全靠对话历史 vs 显式状态机 vs 图结构
多 agent 单 agent vs 多 agent 协作 vs 主从
记忆 全塞上下文 vs RAG vs 结构化存储

15.5 业界主流的"参考实现"(是参照,不是标准)

类别 代表 特点
框架 LangChain / LlamaIndex 生态大,但抽象重、常被吐槽过度封装
图驱动 LangGraph 用状态图管理流程,适合复杂 agent
轻量 直接手写 loop 大厂常见------框架越大越不信框架
厂商 SDK OpenAI/Anthropic 官方 SDK 贴近原生,不绑架
工作流 Dify / Coze 等 低代码编排,面向业务人员
强平台 各云厂商 agent 平台 托管 + 沙箱 + 全栈

一个有趣的行业现象:

越是有深度的团队,越倾向于"手写最小 loop + 自己搭基础设施",而不是用现成重框架。

因为框架的抽象成本,常常高于自己实现核心循环的成本------agent 的核心循环其实就几十行。

15.6 "最佳实践"到底存不存在------分层回答

层面 有没有最佳实践
架构骨架 ✅ 有(五层 + ReAct 循环)
工具协议 ✅ 有(MCP 正在成为事实标准)
工程细节 ⚠️ 有局部最佳实践(沙箱、流式、重试、可观测)
整体方案 ❌ 没有,取决于场景
"该不该用多 agent" ❌ 争议巨大,甚至有"多 agent 是伪需求"的观点

15.7 ★ 追"最佳实践"本身有个陷阱

agent 领域的"最佳实践"更新极快------今年的最佳实践,明年可能是反模式。

曾经的"最佳实践" 现在被质疑
上多 agent 协作 "大部分场景单 agent 更好"
用重框架(LangChain) "抽象太重,不如手写"
把历史全塞上下文 "上下文要精细管理"
复杂规划器 "简单 ReAct 往往更稳"

正确姿势不是"追最佳实践",而是"抓住不变量,其余保持可替换"。

不变量(值得学):

  • ReAct 循环
  • 工具用 schema 描述
  • 沙箱隔离
  • 关注点分离

可变量(别死磕):

  • 用什么框架
  • 用什么记忆方案
  • 单 agent 还是多 agent
  • 用什么编排方式

15.8 落地建议

步骤 做法
1 先写最小循环------不上框架,手写 ReAct loop(那几十行)
2 工具层按 MCP 精神设计------schema 描述,为日后接外部做准备
3 沙箱从简到繁------先子进程,再容器/K8s
4 记忆先不做复杂------短期用对话历史,需要长期再上 RAG
5 框架只当参照不当依赖------读 LangChain/LangGraph 学思路,核心自己控

核心:把"骨架"学扎实,"细节"保持可替换------这样无论业界怎么变,你都不会被淘汰。

15.9 一句话收口

业界没有统一的"最佳 agent 实现方案"。有的是:趋同的骨架(五层 + ReAct 循环)、渐成事实标准的协议(MCP)、以及各自摸索的细节。越成熟的团队越倾向"手写最小循环 + 自建基础设施"。正确姿势不是追最佳实践,而是抓住不变量、把可变量做成可替换。


结语:一张总图串起全部

#mermaid-svg-nD6KWOK0bkh1rTEh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nD6KWOK0bkh1rTEh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nD6KWOK0bkh1rTEh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nD6KWOK0bkh1rTEh .error-icon{fill:#552222;}#mermaid-svg-nD6KWOK0bkh1rTEh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nD6KWOK0bkh1rTEh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nD6KWOK0bkh1rTEh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nD6KWOK0bkh1rTEh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nD6KWOK0bkh1rTEh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nD6KWOK0bkh1rTEh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nD6KWOK0bkh1rTEh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nD6KWOK0bkh1rTEh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nD6KWOK0bkh1rTEh .marker.cross{stroke:#333333;}#mermaid-svg-nD6KWOK0bkh1rTEh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nD6KWOK0bkh1rTEh p{margin:0;}#mermaid-svg-nD6KWOK0bkh1rTEh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nD6KWOK0bkh1rTEh .cluster-label text{fill:#333;}#mermaid-svg-nD6KWOK0bkh1rTEh .cluster-label span{color:#333;}#mermaid-svg-nD6KWOK0bkh1rTEh .cluster-label span p{background-color:transparent;}#mermaid-svg-nD6KWOK0bkh1rTEh .label text,#mermaid-svg-nD6KWOK0bkh1rTEh span{fill:#333;color:#333;}#mermaid-svg-nD6KWOK0bkh1rTEh .node rect,#mermaid-svg-nD6KWOK0bkh1rTEh .node circle,#mermaid-svg-nD6KWOK0bkh1rTEh .node ellipse,#mermaid-svg-nD6KWOK0bkh1rTEh .node polygon,#mermaid-svg-nD6KWOK0bkh1rTEh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nD6KWOK0bkh1rTEh .rough-node .label text,#mermaid-svg-nD6KWOK0bkh1rTEh .node .label text,#mermaid-svg-nD6KWOK0bkh1rTEh .image-shape .label,#mermaid-svg-nD6KWOK0bkh1rTEh .icon-shape .label{text-anchor:middle;}#mermaid-svg-nD6KWOK0bkh1rTEh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nD6KWOK0bkh1rTEh .rough-node .label,#mermaid-svg-nD6KWOK0bkh1rTEh .node .label,#mermaid-svg-nD6KWOK0bkh1rTEh .image-shape .label,#mermaid-svg-nD6KWOK0bkh1rTEh .icon-shape .label{text-align:center;}#mermaid-svg-nD6KWOK0bkh1rTEh .node.clickable{cursor:pointer;}#mermaid-svg-nD6KWOK0bkh1rTEh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nD6KWOK0bkh1rTEh .arrowheadPath{fill:#333333;}#mermaid-svg-nD6KWOK0bkh1rTEh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nD6KWOK0bkh1rTEh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nD6KWOK0bkh1rTEh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nD6KWOK0bkh1rTEh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nD6KWOK0bkh1rTEh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nD6KWOK0bkh1rTEh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nD6KWOK0bkh1rTEh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nD6KWOK0bkh1rTEh .cluster text{fill:#333;}#mermaid-svg-nD6KWOK0bkh1rTEh .cluster span{color:#333;}#mermaid-svg-nD6KWOK0bkh1rTEh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nD6KWOK0bkh1rTEh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nD6KWOK0bkh1rTEh rect.text{fill:none;stroke-width:0;}#mermaid-svg-nD6KWOK0bkh1rTEh .icon-shape,#mermaid-svg-nD6KWOK0bkh1rTEh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nD6KWOK0bkh1rTEh .icon-shape p,#mermaid-svg-nD6KWOK0bkh1rTEh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nD6KWOK0bkh1rTEh .icon-shape .label rect,#mermaid-svg-nD6KWOK0bkh1rTEh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nD6KWOK0bkh1rTEh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nD6KWOK0bkh1rTEh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nD6KWOK0bkh1rTEh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 平台/部署层
A. Agent 运行沙箱 (宿主/容器/云VM)
agent 程序

(调LLM / 解析 / 调度)
工具注册表
内置工具 fn

本地直调
MCP 代理 fn

网络RPC
本地执行代码
⑧ 远程沙箱服务

(HTTP/gRPC)
B. 代码执行沙箱

(容器/K8s Job)

跑不可信脚本
沙箱执行框架

准备/跑/捕获/清理
⑨ 产物回传

共享卷 / 对象存储 / 回调
隔离墙(隔宿主/别人/你的设备)

四句话收尾

  1. 工具系统 :内置工具是同进程函数调用,MCP 工具是网络 RPC------统一在调度,分叉在执行。
  2. 沙箱有两种 :A 是 agent 住的房子(部署层给),B 是关不可信代码的笼子(可能要自己写)------同名不同物,方向相反。
  3. 落地关键 :K8s Job 是云原生拉沙箱的正解;依赖不全靠"预置 + 动态构建(哈希缓存)+ LLM 自愈"三件套兜底------不赌装全,而是设计好缺了怎么办。
  4. 分布式沙箱 :agent 不必自己拉容器,可以调远程沙箱服务 ;产物回传分拉取式/推送式 两大范式;上传等基础设施杂务一律交给沙箱执行框架 ,绝不让不可信代码碰凭证------关注点分离,让业务代码只关心业务。

一以贯之的核心主线

LLM 只负责"决定",agent 运行时负责"调度",工具负责"执行",沙箱负责"隔离"。

四层各司其职,链条清晰------这就是一个 agent 从"想"到"做"的完整骨架。

全篇结构索引

部分 主题
一 工具系统:分类与设计原则
二 内置工具 vs MCP 工具
三 调度链路:一次工具调用怎么发生
四 沙箱基础:职责与隔离分级
五 ★ 两种沙箱(核心对照)
六 文件进出的真相
七 程序、环境、沙箱三者关系
八 代码执行沙箱的实现方案
九 容器的创建与环境准备
十 Agent 在沙箱里拉起沙箱容器(含远程能力辨析)
十一 沙箱的依赖管理
十二 ★ 远程沙箱服务
十三 ★ 产物回传
十四 ★ 沙箱执行框架
十五 ★ Agent 实现方案的共识与分歧(工程选型视角)

一以贯之的核心主线

LLM 只负责"决定",agent 运行时负责"调度",工具负责"执行",沙箱负责"隔离"。

四层各司其职,链条清晰------这就是一个 agent 从"想"到"做"的完整骨架。

相关推荐
鱼饼Y4 小时前
AI Agent 驾驭指南
agent·ai编程
BlackStar_L4 小时前
第五章 Coding Agent 与通用 Agent
大模型·llm·agent
Blockbuater_drug4 小时前
protein-design-mcp 裸机实战:41 个蛋白设计工具大合集Agent部署与实测
agent·mcp·proteinmpnn·rfdiffusion·esmfold·boltz-2
用户79457223954134 小时前
当卖 token 的遇上修高速公路的:AI 周期的真空期在哪
人工智能·agent
程序员老赵4 小时前
Docker 部署 OpenViking:轻松搭建 AI Agent 上下文数据库平台
docker·开源·agent
费曼学习法4 小时前
无人值守AI内容矩阵实战(2):我给 Agent 装了个「声称检测器」,专治它谎报已发布
llm·agent·ai编程
AI小白Lin4 小时前
我的 Agent 迁移"成功"了:每道门都在册,没有一道能跑
架构·llm·agent
lucas_AI5 小时前
给 Coding Agent 塞文档前先看这篇:什么时候是救命稻草,什么时候帮倒忙
llm·agent
欣欣之王来了5 小时前
国外主流大模型深度对比:GPT、Claude、Gemini技术细节与实战选型指南
人工智能·ai·大模型