
你可能已经见过这种场面AI Agent 帮你写了一段看起来完全合理的 Python 脚本然后在你没细看的情况下直接执行了。结果要么删了不该删的文件要么把内网配置读了出来更常见的是脚本陷入死循环把开发机卡到鼠标都挪不动。我在自己的 Agent 项目里把“代码执行”拆成独立模块之前就实打实踩过这种坑。今天这篇内容围绕一个我在生产环境跑了大半年的方案展开把 Agent 生成的代码投进函数计算构建的代码沙箱里让 AI Agent 真正“跑”在云端但每一行代码都撞不到你的真实环境。如果你正好在做 Agent 的代码执行能力又不想为此维护一套容器集群这篇很值得顺着读下去。先交代这个方案解决了什么问题。AI Agent 的核心能力之一是根据任务动态生成代码并执行。可模型生成的代码天然不可控我们不能默认它是安全的更不能让它运行在宿主机上。用函数计算做沙箱底座主要是把“执行环境隔离”“资源上限管控”“生命周期管理”这三件麻烦事交给平台而我们只需要把注意力收窄到“接住代码、运行代码、返还结果”这一个小函数上。下面从安全问题的根源开始把方案选型、核心设计、实操步骤和上线后那些坑全部聊透。1. 为什么 Agent 的执行能力会变成安全隐患1.1 模型生成代码的过程本质上是概率输出想理解沙箱为什么是必需品得先接受一个事实大模型生成的代码只是一个高度拟合训练数据的文本序列不是经过编译器验证和人工审计的可靠程序。模型很可能因为上下文里的一个误导性描述就生成一段执行危险操作的代码也可能因为训练数据里见过某些“快捷写法”就把一堆危险调用带了出来。我实际遇到的案例有这么几类误删型模型为了“清理临时文件”直接生成shutil.rmtree指向了错误目录差点把项目备份一起清掉。越权读取型为了“排查环境问题”代码里包含读取/etc/passwd、环境变量、数据库连接串等操作。资源耗尽型一个看似无害的while True 不断append的列表把服务器的内存瞬间吃满。外联型模型生成代码主动请求不受控的外部地址把内部信息通过请求带出去。这类问题如果在人工写代码的场景里可以通过 Code Review 拦住但在 Agent 场景里代码是高频、自动、批量生成的人工逐条审阅完全不现实。所以必须从架构上强制设置一道边界代码可以在一个受限环境里跑但它的影响范围必须被关在笼子里。1.2 三种沙箱方案的对比我最终选了谁业界做代码沙箱常见的路线有三类我先把它们拉到一个表里对比。方案隔离强度运维成本适合场景进程级沙箱subprocess 资源限制弱共享内核与文件系统低原型验证不能上生产容器级沙箱Docker/gVisor/Firecracker强但需自主维护实例、网络、存储高长任务、重依赖、企业级环境托管函数计算FaaS平台级隔离实例间相互独立低平台托管Agent 短任务、按次执行、弹性伸缩进程级方案实现最快但隔离太弱。子进程跟父进程共享同一个内核和大部分系统资源权限稍微没处理好越权行为就能穿透边界。我在本地测试时模型代码一旦写了删除目录的操作直接作用在我的开发目录上稍微一失手就是灾难。容器级方案隔离效果好但对个人开发者和中小团队来说太重了。得自己搭容器管理平台、配置网络策略、处理实例伸缩、维护存储卷还需要考虑如何限制 CPU、内存、文件系统、系统调用这些全做明白的工程量不小有些经验不足的团队甚至会因为 seccomp 配置出错导致沙箱形同虚设。函数计算刚好卡在中间实例隔离和资源配额由平台负责底层容器的基础设施完全不用我们管我们只写业务逻辑。它有一个明显限制是单次请求的执行时长有上限。但 Agent 生成的代码大多是“计算一下、处理个文件、调个接口”这种短任务几十秒内能完成的比例很高。只要选型前确认任务特征匹配函数计算就是所有方案里性价比最高的一个。2. 函数计算凭什么能当代码沙箱的底座2.1 平台自带的隔离边界省掉最难的底层部分函数计算的底层是容器实例。每次请求被分配到一个实例里执行实例与实例之间的 CPU、内存、文件系统、网络栈是隔离的。平台通过底层的 cgroup 等技术限制资源配额当函数实例的内存使用超过配置值时会把实例直接回收掉而不是让它在宿主机上慢慢拖垮其他任务。这个特性天然符合沙箱的执行预期就算模型代码里写了一个不断申请内存的炸弹最多也只是把自己所在的实例炸掉对整个宿主机和其他租户没有影响。我记得第一次测试时故意写了一段while True:不断创建大列表的代码配置 512MB 内存几秒钟后实例就被平台强制回收了随后收到执行错误整个过程非常干净。另外函数实例的生命周期是短暂的。平台会根据请求并发自动创建和销毁实例不会有一个常驻的进程池。所以即使代码执行出了意外影响范围也被限制在请求生命周期内请求一结束实例资源就被释放。这一点让“沙箱”这个词在函数计算里体现得非常充分。2.2 按调用计费的模式和 Agent 的使用特征完美吻合Agent 场景里沙箱调用是一个典型的突发短任务模型用户交互期间连续几次调用沙箱执行代码其余时间基本空闲。如果为这个场景去租一台长期运行的服务器空置成本会很高。函数计算的计费模式是按调用次数和实际执行时间计算资源消耗没有请求时不产生费用。我自己的数据比较有代表性一个日活几百人的 Agent 应用代码执行部分平均每天被调用几千次每次执行时间集中在几秒到几十秒一个月的沙箱费用非常可控。早期我评估过自己租一台 2C4G 的云主机专门跑沙箱月成本要高好几倍还得搭环境、打补丁、做监控。函数计算把成本结构变成了真正的“按量付费”对不确定规模的项目来说更加安全。2.3 选型前先正视函数计算的三个边界函数计算不是万能的选它之前必须清楚它有三个边界第一个是执行时长限制。不同产品不同配置单次请求执行时间超过上限就会被中断。如果 Agent 需要跑一个 20 分钟的数据分析脚本那不适合直接塞进函数计算要么拆成子任务要么单独放到更重的计算服务里。第二个是临时存储有限。函数实例的临时磁盘一般有上限比如 512MB 到几 GB 不等且不持久。如果需要处理很大的数据集得考虑对象存储中转或者流式处理不能指望在沙箱里落下大量中间文件。第三个是实例无状态。两次请求之间不保证共享内存和本地磁盘Agent 的上一次执行状态不会自动保留。所以状态要么放在外部存储要么让每次执行自带足够的上下文。把这三个边界想清楚函数计算沙箱才能用得顺手否则后面会不断被“为什么任务跑不了”这种问题缠住。3. 动手之前先把沙箱的接口和安全基线定清楚3.1 接口设计只留一个“代码进出”的洞开发沙箱函数之前先把 Agent 和沙箱之间的协议定下来。我的原则是最小化调用方只传语言类型、代码内容和期望的超时时间沙箱只返回执行结果和错误信息不暴露任何内部细节。这样对模型最友好也最容易排查问题。接口请求体长这样{ lang: python, code: print(hello from agent), timeout: 30 }统一返回结构{ ok: true, returncode: 0, stdout: hello from agent\n, stderr: }出错时增加error字段。这套协议简洁明了。Agent 框架把“执行代码”暴露成一个工具函数模型只需要生成代码文本作为参数不用关心沙箱内部怎么运转。3.2 超时、内存、输出长度三个关键参数的计算逻辑参数不能拍脑袋定我一贯的做法是基于线上数据反推。先说超时。Agent 产生的代码任务一般在几秒到几十秒之间我建议先设置一个默认 30s 的超时跑一段时间后看线上执行的耗时分布把超时设成 P95 耗时的两倍左右。留这层缓冲是为了避免把正常但稍微复杂的运算误杀同时也不会放跑真正失控的任务。输出长度同样要卡。模型上下文窗口有限沙箱执行结果如果无限往上堆轻则浪费 token重则直接把 Agent 的上下文塞爆。我把 stdout 和 stderr 分别截断到 64KB超过部分用[output truncated]标识。这个值足够覆盖绝大多数正常执行输出同时不会冲击 LLM 的上下文长度。内存参数则要看任务类型。基础计算 256MB 够用涉及 pandas、numpy 这类库的计算任务建议给到 512MB 起步。内存不是越大越好配置越高单次调用的费用和对宿主机的占用也越高。按任务粒度分两个档位足够不需要搞特别细的配置管理。3.3 环境变量和临时目录的清理策略沙箱执行时模型代码能接触到的环境变量越少越好。函数计算运行时会注入一些平台相关的环境变量但也可能包含我们部署时配置的自定义变量其中如果有数据库连接串、API Token 这类信息模型代码一旦通过os.environ读取到了就会造成敏感信息泄露。我在 handler 里做了一层过滤只保留函数计算框架必需的FC_前缀变量其余全部清空再传给子进程。这样即使函数配置里有密钥模型代码运行时也拿不到。临时目录方面所有模型生成的代码和中间文件都统一放到/tmp/agent-sandbox下与函数自身目录隔离避免污染部署产物。还有个容易忽略的坑函数实例的 /tmp 目录在实例生命周期内是复用的。如果前一次执行生成了文件后一次执行时还在模型代码就有可能读到“上一个任务”的残留数据。所以每次执行前先清理一次工作目录是一个非常好的习惯。4. 完整实操从零搭一个可上线的沙箱函数4.1 创建函数计算服务与运行环境实际操作时我建议单独建一个服务不要和业务函数混在一起。独立服务的好处是日志、权限、监控都能独立管理出问题时排查范围更清晰。运行时选择 Python内存先按 256MB 配置执行超时根据需求设置我习惯先给 60s后面按实际分布再调。部署方式有两种主流选择直接上传代码包或者使用自定义镜像。如果只需要 requests、pandas 这类常见依赖代码加依赖打包成 ZIP 上传比较轻量。如果需要更复杂的系统级库就要走自定义镜像。第一条路门槛低适合快速验证。4.2 Python handler 的实现与关键安全开关沙箱函数的 handler 核心逻辑并不复杂接收代码 → 写入临时文件 → 用 subprocess 执行 → 收 stdout/stderr → 返回结果。关键在几个安全开关上我直接贴一份简化版参考实现。import subprocess import os import json MAX_OUTPUT 64 * 1024 WORK_DIR /tmp/agent-sandbox def handler(event, context): req json.loads(event) code req.get(code, ) lang req.get(lang, python) timeout min(int(req.get(timeout, 30)), 120) if lang ! python: return {ok: False, error: funsupported lang: {lang}} # 只保留平台运行必需的环境变量 env {k: v for k, v in os.environ.items() if k.startswith(FC_)} os.makedirs(WORK_DIR, exist_okTrue) # 清理上次执行可能残留的文件 for f in os.listdir(WORK_DIR): os.remove(os.path.join(WORK_DIR, f)) script_path os.path.join(WORK_DIR, main.py) with open(script_path, w, encodingutf-8) as f: f.write(code) try: res subprocess.run( [python3, -I, script_path], cwdWORK_DIR, capture_outputTrue, timeouttimeout, envenv, ) stdout (res.stdout or b)[:MAX_OUTPUT].decode(utf-8, errorsreplace) stderr (res.stderr or b)[:MAX_OUTPUT].decode(utf-8, errorsreplace) return { ok: res.returncode 0, returncode: res.returncode, stdout: stdout, stderr: stderr, } except subprocess.TimeoutExpired: return {ok: False, error: execution timeout} except Exception as e: return {ok: False, error: str(e)}两个细节必须单独说。第一python3 -I表示 Python 的隔离模式启动时不会把当前目录自动加入sys.path。这样做可以防止模型生成一个与标准库同名的文件导致 import 时被劫持。第二env变量只保留FC_前缀把密钥和敏感自定义变量挡在子进程之外。注意这里只是第一道过滤更稳妥的方案是部署时根本不给沙箱函数注入可用密钥。4.3 通过 HTTP 触发并做好访问控制沙箱函数创建后配置一个 HTTP 触发器Agent 服务就能通过普通的 HTTP POST 请求来调用。这个环节最大的坑是访问控制。如果函数以公网 URL 暴露且没有鉴权任何人都能往你的沙箱里投递代码等于你帮别人开了一台免费的云服务器。后果不仅是费用损失还可能被用于恶意计算。解决方案是开启动态鉴权。函数计算一般支持签名 URL 或 JWT 鉴权Agent 侧在调用时把私有 Token 加到请求头里函数侧校验通过后才执行。在沙箱这类高风险接口上鉴权不能省签名算法也要选强一些的比如 JWT 配合 RSA 或 HMAC-SHA256。我在实际项目里是把沙箱函数的调用权限收敛到服务账号不对外暴露永久 URL这样即使 Agent 侧被攻破攻击者拿到的也只是一个受限凭证。4.4 Agent 侧代码封装和工具调用逻辑Agent 侧调用沙箱的代码不复杂重点是封装成模型可以直接使用的工具。参考实现如下import requests SANDBOX_URL https://your-service-url/functions/sandbox/invocations SANDBOX_TOKEN your-token def run_agent_code(code: str, timeout: int 30) - str: resp requests.post( SANDBOX_URL, json{lang: python, code: code, timeout: timeout}, headers{Authorization: fBearer {SANDBOX_TOKEN}}, timeouttimeout 10, # 外部超时要大于函数超时 ) data resp.json() if not data.get(ok): return f[sandbox error] {data.get(error, unknown)} if data.get(stderr): return f[stderr] {data[stderr]}\n[stdout] {data[stdout]} return data.get(stdout, )把这个run_agent_code注册为 Agent 的工具函数后模型只需要生成包含代码参数的调用即可。还有一个处理细节值得分享回传模型的内容需要做截断。我一般先把 stdout 截到 2KB如果长度超了就只保留开头和结尾各 1KB并在中间加[truncated]提示stderr 则尽量完整因为模型要根据报错来修正代码。错误信息往往比正常输出更有价值这个顺序不要搞反。4.5 限制网络访问范围在沙箱里跑模型代码网络权限是另一个容易疏漏的地方。默认情况下如果函数计算配置了公网访问能力模型代码就可以直接请求外部地址。从安全角度看大部分 Agent 代码执行任务根本不需要访问公网只需要本地计算。所以上线前我建议把沙箱函数放入独立网络环境并配置网络策略仅放行必要的域名。如果模型代码确实需要调用第三方 API更安全的做法是把外部 API 封装成 Agent 的专用工具函数在封装层做鉴权、限流、响应校验而不是让模型直接生成裸的 HTTP 请求。这样即使模型发起了不当请求也会被封装层拦截。5. 上线之后一定会遇到的那些问题5.1 冷启动导致响应慢怎么找平衡函数计算在实例没有预热时首个请求会发生冷启动耗时可能从几百毫秒涨到几秒。Agent 场景对交互延迟敏感冷启动会被明显感知。我常用的有三招第一配置最小实例数或预留实例。让平台保持少量常驻实例冷启动自然减少缺点是会增加少量空转费用。第二给沙箱函数设置定时健康检查或定时触发人为维持实例存活。第三在 Agent 的用户体验层接受冷启动把“代码执行中”的状态展示做好让用户理解需要等几秒。前期用户量不大时第三招最实际。等调用量上来了再用预留实例把 P95 延迟压下去不要一上来就为了消灭冷启动砸钱。5.2 函数超时了但代码明明很快为什么这类问题我排查了很多次常见原因有两种。第一种是模型代码里真的写了死循环或同步等待比如一个永不退出的while True、或一个等待网络响应的同步请求。第二种是函数内 fork 了子进程调用函数返回时子进程还在后台跑导致请求被判定为超时。第二种情况尤其隐蔽。函数计算的超时机制主要针对主进程subprocess.run设置超时也只能约束它启动的直接子进程。如果代码内部再 spawn 出更下层的进程主进程退出后这些进程可能变成“孤儿进程”继续执行。解决思路是使用进程组管理超时后把整个进程组一起干掉同时在沙箱里通过限制进程创建等能力禁止模型代码任意 fork。5.3 依赖装不上、运行时报 GLIBC 版本错误这是做自定义镜像或者 layer 之前最常踩的坑。本地开发时 Python 版本是 3.10函数计算的运行时是 3.9两者编译出来的 wheel 包不兼容尤其像 pandas、numpy 这类带二进制依赖的库非常容易部署后 import 失败报 GLIBC 版本错误。正确做法是在构建依赖包时用与目标运行时一致的基础镜像来安装依赖。不要直接拿本地pip install出来的结果上传。容器构建时指定与函数运行时匹配的 Python 版本和基础系统装完依赖后再打包成 layer 或镜像。这个流程前期会多花一些时间但从源头上避免了运行时不一致的问题。5.4 怎么验证沙箱真的起到了隔离效果隔离这件事最怕“我以为它隔离了”。我每次调整安全配置后都会做一轮固定流程的自测在沙箱里执行os.listdir(/)确认看到的文件系统和业务服务目录不一致。尝试读取常见的敏感文件如/etc/passwd确认无异常可读或虽可读但得到的是容器内数据。尝试访问内网地址确认网络策略生效。执行一段申请大内存的代码确认实例被平台回收。检查子进程能否访问环境变量中的 Token确认过滤逻辑有效。这些自测脚本我会存成固定测试用例每次部署新版本后自动跑一遍。只有全部通过才会把新版本放量上线。5.5 日志和监控是排查一切问题的第一现场沙箱函数上线后必须第一时间把日志打开。函数计算控制台能看到每次调用的请求 ID、执行耗时、错误堆栈。我习惯在函数里把每次执行的代码摘要、stdout 截断、stderr 截断都作为结构化日志打出来。这样一旦线上出了问题可以直接通过请求 ID 把一次完整执行链路捞出来快速定位是模型生成逻辑的问题还是沙箱环境的问题。同时建议配置超时和错误告警。比如某类代码反复触发超时时告警会提示我去检查是不是模型近期生成模式发生了变化。有了日志和数据沙箱的参数调整就不再靠拍脑袋而是有真实的业务分布作为依据。这也是整个沙箱方案从“能用”走向“好用”的关键一步。结尾我个人在实际操作中的体会是Agent 的安全不能指望某一个单点机制来解决它需要把平台隔离、函数内限制、调用侧鉴权、日志观测四层叠在一起。函数计算做代码沙箱最大的价值就是帮我们把最底层的隔离问题接走让我们把精力集中在 Agent 侧的规则、接口和结果处理上。如果你正在做 Agent 的代码执行能力可以按这个顺序落地先让最小执行函数跑通再补访问鉴权和网络限制最后把日志和监控接上。沙箱的安全边界是越用越清晰的东西早点让它跑起来比在文档里反复推演更有效率。