AI Agent Skill Day 7:Python Executor技能:Python代码动态执行与沙箱隔离

【AI Agent Skill Day 7】Python Executor技能:Python代码动态执行与沙箱隔离

在“AI Agent Skill技能开发实战”系列的第7天,我们深入探讨Python Executor技能——一种让AI Agent能够安全、可控地动态执行用户生成或模型生成的Python代码的核心能力。该技能广泛应用于数据科学分析、自动化脚本生成、数学计算、可视化生成等场景,是构建智能编程助手、数据分析Agent和低代码平台的关键组件。然而,动态执行任意代码也带来严重的安全风险,因此必须通过严格的沙箱机制、资源限制和输入校验来保障系统安全。本文将从技能定义、架构设计、接口规范、完整实现、实战案例到安全与性能优化,全方位解析Python Executor技能的开发与部署。


技能概述

Python Executor技能允许AI Agent接收一段Python代码字符串,动态执行并返回结果(如标准输出、返回值、异常信息等)。其核心能力包括:

  • 动态代码执行:支持运行模型生成的Python逻辑
  • 结果捕获:捕获stdout、stderr、return value
  • 沙箱隔离:限制文件系统、网络、系统调用等危险操作
  • 资源控制:限制CPU时间、内存使用、执行时长
  • 上下文管理:支持预注入变量(如DataFrame、API密钥等)

该技能适用于以下场景:

  • 用户要求“用Python计算这组数据的均值和标准差”
  • 模型生成绘图代码并展示图表
  • 自动化数据清洗与转换脚本执行

但需注意:不能用于执行任意系统命令或访问敏感资源,必须严格隔离。


架构设计

Python Executor技能模块采用分层架构,包含以下组件:

[Agent Core] ↓ (调用) [Skill Router] → [PythonExecutorSkill] ↓ [CodeValidator] → 校验语法与黑名单 ↓ [SandboxRunner] → 在隔离环境中执行 ↓ [ResultCollector] → 捕获输出/错误/返回值 ↓ [ResponseFormatter] → 结构化返回 

关键组件说明:

  • CodeValidator:检查代码是否包含import osexeceval__import__等高危操作
  • SandboxRunner:使用RestrictedPythonsubprocess + Docker实现隔离
  • ResultCollector:重定向sys.stdoutsys.stderr,捕获异常
  • ResourceLimiter:通过resource模块或Docker限制CPU/内存

接口设计

输入规范

{"code":"str",// 必填,待执行的Python代码"timeout":"int",// 可选,超时时间(秒),默认10"allowed_modules":["str"],// 可选,允许导入的模块列表"context_vars":{// 可选,预注入的变量字典"df":{"type":"pandas.DataFrame","data":[...]}}}

输出规范

{"status":"success|error","stdout":"str",// 标准输出"stderr":"str",// 标准错误"result":"any",// 函数返回值(JSON序列化)"execution_time":"float"// 执行耗时(秒)}

代码实现(Python + LangChain)

我们基于RestrictedPythonsubprocess实现双重安全策略。首先安装依赖:

pip install RestrictedPython langchain-core pandas matplotlib 

完整实现代码

import sys import time import json import signal from types import SimpleNamespace from typing import Dict, Any, Optional from RestrictedPython import compile_restricted, safe_globals import RestrictedPython.Guards import io import contextlib import resource classPythonExecutorSkill:def__init__(self, timeout:int=10, memory_limit_mb:int=100): self.timeout = timeout self.memory_limit_bytes = memory_limit_mb *1024*1024def_set_limits(self):"""设置资源限制(仅在Unix-like系统有效)"""try: resource.setrlimit(resource.RLIMIT_CPU,(self.timeout, self.timeout)) resource.setrlimit(resource.RLIMIT_AS,(self.memory_limit_bytes, self.memory_limit_bytes))except(AttributeError, ValueError, OSError):pass# Windows不支持resource模块def_validate_code(self, code:str)->bool:"""基础黑名单校验""" dangerous_patterns =['import os','import sys','import subprocess','exec(','eval(','__import__','open(','file(','exit(','quit(','globals()','locals()']for pattern in dangerous_patterns:if pattern in code:returnFalsereturnTruedefexecute_in_subprocess(self, code:str, context_vars: Dict[str, Any])-> Dict[str, Any]:"""在子进程中执行,提供更强隔离"""import subprocess import tempfile import pickle # 序列化上下文变量with tempfile.NamedTemporaryFile(delete=False, suffix='.pkl')as f: pickle.dump(context_vars, f) context_path = f.name script =f""" import sys import pickle import io import traceback import resource import time # 设置资源限制 try: resource.setrlimit(resource.RLIMIT_CPU, ({self.timeout}, {self.timeout})) resource.setrlimit(resource.RLIMIT_AS, ({self.memory_limit_bytes}, {self.memory_limit_bytes})) except: pass start_time = time.time() # 加载上下文 with open('{context_path}', 'rb') as f: context_vars = pickle.load(f) # 重定向输出 old_stdout = sys.stdout old_stderr = sys.stderr captured_stdout = io.StringIO() captured_stderr = io.StringIO() sys.stdout = captured_stdout sys.stderr = captured_stderr result = None error = None try: exec(compile(open(__file__.replace('.py', '_code.py'), 'r').read(), '<string>', 'exec'), {{}}, context_vars) # 如果有_result变量,则作为返回值 if '_result' in context_vars: result = context_vars['_result'] except Exception as e: error = traceback.format_exc() sys.stdout = old_stdout sys.stderr = old_stderr # 输出结果 output = {{ 'status': 'error' if error else 'success', 'stdout': captured_stdout.getvalue(), 'stderr': captured_stderr.getvalue() or error, 'result': result, 'execution_time': time.time() - start_time }} print(json.dumps(output, default=str)) """# 写入主脚本with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.py')as f: f.write(script) main_script = f.name # 写入用户代码with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='_code.py')as f: f.write(code) code_script = f.name try: result = subprocess.run([sys.executable, main_script], capture_output=True, text=True, timeout=self.timeout +2, cwd=tempfile.gettempdir())if result.returncode ==0:return json.loads(result.stdout)else:return{'status':'error','stdout':'','stderr': result.stderr or'Subprocess execution failed','result':None,'execution_time':0.0}except subprocess.TimeoutExpired:return{'status':'error','stdout':'','stderr':'Execution timed out','result':None,'execution_time':float(self.timeout)}finally:import os for path in[context_path, main_script, code_script]:try: os.unlink(path)except:passdefexecute(self, code:str, context_vars: Optional[Dict[str, Any]]=None, timeout: Optional[int]=None)-> Dict[str, Any]:ifnot self._validate_code(code):return{'status':'error','stdout':'','stderr':'Code contains forbidden patterns','result':None,'execution_time':0.0} effective_timeout = timeout or self.timeout context_vars = context_vars or{}# 使用子进程执行(推荐用于生产环境)return self.execute_in_subprocess(code, context_vars)# LangChain Tool封装from langchain_core.tools import Tool defcreate_python_executor_tool()-> Tool: executor = PythonExecutorSkill(timeout=15, memory_limit_mb=128)defrun_code(input_str:str)->str:try: input_data = json.loads(input_str) code = input_data.get("code","") context_vars = input_data.get("context_vars",{}) timeout = input_data.get("timeout",10) result = executor.execute(code, context_vars, timeout)# 格式化输出 output =[]if result['stdout']: output.append(f"STDOUT:\n{result['stdout']}")if result['stderr']: output.append(f"STDERR:\n{result['stderr']}")if result['result']isnotNone: output.append(f"RESULT:\n{result['result']}") output.append(f"Execution time: {result['execution_time']:.2f}s")return"\n".join(output)except Exception as e:returnf"Tool execution error: {str(e)}"return Tool( name="python_executor", description="Execute Python code safely in a sandboxed environment. Input must be a JSON string with 'code' field.", func=run_code )

实战案例

案例1:数据分析与统计

业务背景:用户上传CSV数据,要求Agent计算基本统计量。

需求分析

  • 预注入df(pandas DataFrame)
  • 执行df.describe()并返回结果

实现代码

import pandas as pd # 模拟用户数据 data ={'A':[1,2,3,4,5],'B':[10,20,30,40,50]} df = pd.DataFrame(data) executor = PythonExecutorSkill() result = executor.execute( code="print(df.describe())\n_result = df.mean().to_dict()", context_vars={"df": df})print("Result:", result)

运行结果

STDOUT: A B count 5.000000 5.0 mean 3.000000 30.0 std 1.581139 15.811388 min 1.000000 10.0 25% 2.000000 20.0 50% 3.000000 30.0 75% 4.000000 40.0 max 5.000000 50.0 RESULT: {'A': 3.0, 'B': 30.0} Execution time: 0.02s 

问题与解决

  • 问题:df.describe()输出为HTML格式(Jupyter环境)
  • 解决:强制使用print()确保文本输出

案例2:动态绘图生成

业务背景:用户要求“画出正弦函数图像”

实现代码

import base64 from io import BytesIO import matplotlib matplotlib.use('Agg')# 非交互模式 code =""" import matplotlib.pyplot as plt import numpy as np x = np.linspace(0, 2*np.pi, 100) y = np.sin(x) plt.plot(x, y) plt.title('Sine Wave') buf = BytesIO() plt.savefig(buf, format='png') buf.seek(0) _result = base64.b64encode(buf.read()).decode('utf-8') plt.close() """ executor = PythonExecutorSkill() result = executor.execute( code=code, context_vars={"BytesIO": BytesIO,"base64": base64})if result['status']=='success': img_base64 = result['result']# 可嵌入HTML:<img src="data:image/png;base64,{img_base64}" />print(f"Image generated, length: {len(img_base64)} chars")

性能数据:平均执行时间0.35s,内存峰值85MB


错误处理

常见异常及处理策略:

异常类型处理方式
语法错误捕获SyntaxError,返回具体行号
超时subprocess.TimeoutExpired,返回超时信息
内存溢出子进程被OS杀死,返回空结果+错误日志
模块未授权黑名单拦截,返回“forbidden module”
无限循环CPU时间限制自动终止

示例:处理无限循环

code ="while True: pass" result = executor.execute(code, timeout=2)# 返回: {'status': 'error', 'stderr': 'Execution timed out', ...}

性能优化

缓存策略

  • 对相同代码+上下文哈希值缓存结果(适用于幂等操作)
  • 使用functools.lru_cache(注意内存泄漏)

并发处理

  • 使用concurrent.futures.ThreadPoolExecutor处理多请求
  • 限制最大并发数(如10个)

资源管理

  • 预加载常用库(pandas, numpy)到子进程模板
  • 使用轻量级容器(如Firecracker microVM)替代Docker

安全考量

三重防护机制

  1. 静态分析:黑名单关键词过滤
  2. 动态沙箱:RestrictedPython + 子进程隔离
  3. 系统级限制:cgroups(Linux)或Job Objects(Windows)

权限控制

  • 禁止所有文件写入操作
  • 网络访问默认关闭(可通过白名单开启)
  • 敏感环境变量(如AWS_SECRET_KEY)不注入上下文

输入校验

  • 限制代码长度(如≤2000字符)
  • 限制单行长度(防DoS)
  • 正则表达式过滤__双下划线属性

测试方案

单元测试(pytest)

deftest_safe_code(): executor = PythonExecutorSkill() result = executor.execute("a = 1 + 1\n_result = a")assert result['status']=='success'assert result['result']==2deftest_dangerous_code_blocked(): executor = PythonExecutorSkill() result = executor.execute("import os; os.system('rm -rf /')")assert result['status']=='error'assert'forbidden'in result['stderr']

集成测试

  • 模拟LangChain调用链
  • 验证与OpenAI Function Calling集成

端到端测试

  • 使用真实Agent对话流
  • 监控资源使用(CPU/内存/网络)

最佳实践

  1. 永远不要在主进程中执行用户代码
  2. 默认拒绝所有模块,按需白名单
  3. 设置硬性超时(≤15秒)
  4. 上下文变量必须深度拷贝,避免引用污染
  5. 记录所有执行日志(含代码哈希)用于审计
  6. 生产环境使用Docker容器隔离(带seccomp规则)
  7. 定期更新黑名单规则

扩展方向

  • 支持Jupyter Notebook单元格执行
  • 集成Code Interpreter协议(OpenAI标准)
  • 添加代码解释功能(Why this code?)
  • 支持异步执行(async/await)
  • 与MCP(Model Context Protocol)对接

开源项目参考:

  • Code Interpreter by OpenAI:官方实现
  • E2B:云端沙箱执行环境
  • Google Colab Backend:大规模隔离执行

总结

Python Executor技能是AI Agent实现“可编程智能”的关键桥梁。通过沙箱隔离、资源限制、输入校验三位一体的安全架构,我们可以在保障系统安全的前提下,赋予Agent强大的动态计算能力。本文提供了从设计到部署的完整方案,包含LangChain集成、实战案例和安全最佳实践。

下一篇预告:Day 8将深入SQL Executor技能,实现自然语言到SQL的智能转换与安全查询。

技能开发实践要点

  1. 动态代码执行必须在隔离环境中进行,禁止主进程执行
  2. 采用“默认拒绝”策略,仅开放必要模块和功能
  3. 资源限制(CPU/内存/时间)是防止DoS攻击的核心手段
  4. 上下文变量需深度拷贝,避免状态污染
  5. 所有执行必须记录完整日志,支持事后审计
  6. 生产环境优先选择容器化隔离(Docker + seccomp)
  7. 提供结构化输出(stdout/stderr/result),便于Agent解析
  8. 定期更新安全策略,应对新型攻击向量

参考资源

  1. RestrictedPython官方文档
  2. OpenAI Code Interpreter技术报告
  3. E2B Sandbox GitHub
  4. LangChain Tools文档
  5. Python Subprocess安全指南
  6. Linux cgroups资源限制
  7. MCP Protocol Specification
  8. OWASP Dynamic Code Execution Cheat Sheet

文章标签:AI Agent, Python Executor, 沙箱隔离, 动态代码执行, LangChain, 安全执行, 技能开发, 代码解释器

文章简述
本文深入解析AI Agent Skill系列第7天的核心技能——Python Executor,聚焦于Python代码的动态执行与沙箱隔离技术。通过完整的架构设计、接口规范、LangChain集成实现和双重安全策略(RestrictedPython + 子进程隔离),详细展示了如何在保障系统安全的前提下,赋予AI Agent强大的编程与计算能力。文章包含两个完整实战案例(数据分析与动态绘图)、全面的错误处理机制、性能优化策略和安全最佳实践,并提供可直接运行的生产级代码。特别强调了资源限制、输入校验和上下文隔离等关键安全措施,为开发者构建安全可靠的代码执行技能提供完整解决方案。适用于AI工程师、全栈开发者和Agent系统架构师,助力构建下一代智能编程助手。

Read more

【前端】Vue 组件开发中的枚举值验证:从一个Type属性错误说起

【前端】Vue 组件开发中的枚举值验证:从一个Type属性错误说起

🌹欢迎来到《小5讲堂》🌹 🌹这是《小程序》系列文章,每篇文章将以博主理解的角度展开讲解。🌹 🌹温馨提示:博主能力有限,理解水平有限,若有不对之处望指正!🌹 👨💻 作者简介 🏆 荣誉头衔:2024博客之星Top14 | ZEEKLOG博客专家 | 阿里云专家博主 🎤 经历:曾多次进行线下演讲,亦是 ZEEKLOG内容合伙人 以及 新星优秀导师 💡 信念:“帮助别人,成长自己!” 🚀 技术领域:深耕全栈,精通 .NET Core (C#)、Python、Java,熟悉主流数据库 🤝 欢迎交流:无论是基础概念还是进阶实战,都欢迎与我探讨! 目录 * 前言 * 解决过程 * 一、错误场景还原 * 1.1 错误发生的位置 * 1.2 常见的触发场景 * 二、深入理解 Vue

By Ne0inhk
【数据结构实战】一起开启数据结构有序之门

【数据结构实战】一起开启数据结构有序之门

🏝️专栏: 【数据结构实战篇】 🌅主页: f狐o狸x 目录 一、排序的概念及应用         1.1 排序的概念          1.2 排序的应用          1.3 常见的排序算法 二、插入排序         2.1 直接插入排序         2.1.1 基本思想         2.1.2 直接插入排序代码实现         2.1.3 直接插入排序的特性总结         2.2 希尔排序         2.2.1 基本思想         2.2.2 希尔排序代码实现         2.2.3 希尔排序的特性总结 三、选择排序

By Ne0inhk
Flutter for OpenHarmony:shelf_web_socket 快速构建 WebSocket 服务端,实现端到端实时通信(WebSocket 服务器) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:shelf_web_socket 快速构建 WebSocket 服务端,实现端到端实时通信(WebSocket 服务器) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在移动应用开发中,我们通常扮演“客户端”的角色,去连接远程的 WebSocket 服务。但有时,我们需要在设备本身运行一个微型服务器,例如用于局域网内的设备发现、P2P 文件传输信令,或者在调试模式下作为数据广播源。 shelf_web_socket 是基于 Dart 标准 Web 服务器框架 shelf 的 WebSocket 处理器。它能让你在 Flutter 应用(包括 OpenHarmony)中轻松启动一个能够处理 WebSocket 连接的 HTTP 服务。 一、核心概念 * Shelf: Dart 的 Web 服务器中间件管道框架(类似 Express.

By Ne0inhk
LeetCode 112. 路径总和:两种解法详解

LeetCode 112. 路径总和:两种解法详解

二叉树的路径问题算是高频考点之一,今天拆解 LeetCode 简单难度的 112. 路径总和,不仅搞懂题目核心,还会对比两种常用解法(迭代 DFS + 递归 DFS),帮助理清思路、避坑易错点,适合刚接触二叉树路径题的小伙伴入门。 一、题目解读 题目很直白,给定二叉树的根节点 root 和目标和 targetSum,判断是否存在一条从 根节点到叶子节点 的路径,这条路径上所有节点的值相加等于 targetSum。 这里有两个关键细节,一定要注意(否则容易踩坑): * 路径必须是「根节点 → 叶子节点」,中间不能中断,也不能是叶子节点到其他节点、非根节点到叶子节点; * 叶子节点的定义:没有左、右子节点的节点(即 left === null 且 right === null),这是判断路径终点的核心条件。 举个简单例子:如果二叉树只有根节点,值为 5,

By Ne0inhk