1. 项目背景与核心定位
1.1 这个项目到底解决什么问题
先说结论:Zorv AI 对话框 Python 执行,本质上是给 AI 对话框装了一个"代码运行沙箱"。用户在对话框里写一段 Python 代码,系统直接执行并返回结果,而不是只给一段代码让用户自己复制到本地跑。
我最初接触这个需求时,第一反应是"这不就是 Jupyter 换个壳吗"。但真正动手才发现,事情远没有这么简单。AI 对话框的最大特点是会话上下文——你在对话框里让 AI 写一段代码,AI 不仅要执行这段代码,还得记住之前聊过的内容,知道上一次运行定义了哪些变量、生成了什么结果。这种"有状态"的交互,比单纯的代码执行工具复杂一个量级。
举个例子:用户先问"帮我读取一下这个 Excel 文件的前 5 行",AI 执行完返回结果;接着用户说"把第 3 列的数据做一下归一化"。这时候系统必须知道"那个 Excel 文件"指的是什么、前 5 行数据存在于哪个变量里。这不是简单的命令逐条执行,而是要求代码执行引擎具备持久化和状态管理能力。
另一个待解决的问题是并发隔离。多位用户同时在对话框里执行 Python 代码,代码之间绝对不能互相影响。如果用户在代码里写了一个 while True 死循环,或者不小心把内存打满,其他用户的会话不能被拖垮。这些都是代码执行引擎在架构层面必须考虑的事。
1.2 适合谁读这篇文档
如果你属于以下任何一类人,这篇文章都有参考价值:
- AI 应用开发者,正在做 AI 对话框的代码解释器功能,想了解执行层的架构设计。
- 后端工程师,需要为自己产品接入代码执行能力,想知道沙箱怎么做、资源怎么控制。
- 技术负责人,在评估"对话式编程"这类功能的可行性和实现成本。
- 对技术架构感兴趣的读者,想搞明白为什么有些 AI 编程工具能做到"写代码 + 跑代码 + 看结果"一条龙。
我会从架构分层、核心模块设计、实际踩坑记录三个层面展开,把我在这个项目中遇到的问题和解决方案完整讲一遍。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体架构与关键设计决策
2.1 三层架构:对话层、执行层、隔离层
整个系统我拆成了三层:
| 层级 | 职责 | 核心组件 | 关键技术点 |
|---|---|---|---|
| 对话层 | 维护用户会话状态、管理消息上下文 | 会话管理器、消息队列 | 上下文窗口、会话持久化 |
| 执行层 | 接收代码、执行、返回结果 | 代码执行引擎、运行时管理 | 状态保留、超时控制、输入输出捕获 |
| 隔离层 | 保证代码在安全沙箱运行 | 容器编排、资源限制 | 容器隔离、内存/CPU 控制、网络策略 |
对话层面向用户,执行层和隔离层对用户透明。用户感知不到沙箱的存在,他只知道在对话框里写代码、点运行、看结果。这个设计最核心的思路是把对话上下文与代码执行状态解耦。
我踩过最大的坑就是把两者耦合在一起。早期版本里,每个会话对应一个 Python 进程,进程里跑的变量就是会话状态。听起来没问题,但论坛上有用户开着 200 个会话,系统直接崩了——200 个 Python 进程同时跑,内存轻松打满。后来改成三层架构,会话状态存数据库,执行状态由执行层的运行时管理,隔离层按需拉起容器,问题才解决。
2.2 为什么选择容器作为沙箱方案
代码执行沙箱有几种常见方案,我逐个对比过后才确定用容器:
方案一:子进程方案。直接在服务端用 subprocess 拉起一个 Python 进程执行代码。优点是简单、启动快,缺点是隔离性差。子进程可以读宿主机文件,可以发起网络请求,一旦代码里有恶意操作(比如读取 /etc/passwd),后果很严重。
方案二:Docker 容器方案。每次执行代码拉起一个临时容器,用完销毁。隔离性好,但启动耗时较长,而且频繁创建销毁容器对宿主机压力不小。
方案三:容器池化方案。预启动一批容器放在池子里,请求来了直接分配一个,用完回收。这个方案兼顾了启动速度和隔离性,但需要自己写调度逻辑。
我最终选择的是方案三。实际项目中,我用 Docker 预创建了一个执行容器池,池容量根据历史请求量动态伸缩。高峰期扩到 10 个容器,低峰期收缩到 2 个。每个容器只挂载必要的临时文件目录,网络策略默认禁止外部访问,只有内网域名白名单可以通过。
有个细节值得提:容器镜像一定要做裁剪,不要装多余的工具包。官方 Python 镜像默认带了一堆东西,攻击面太大。我用的镜像精简到只剩 Python 运行时、pip、以及预先装好的常用依赖库,删掉了 curl、wget 这类网络工具,哪怕容器被攻破,攻击者也没工具可用。
2.3 状态管理:让 AI 记住上次的代码结果
这是整个项目里最考验设计能力的地方。用户希望 AI 能像 Jupyter 一样记住变量状态,但又不能真的为每个用户开一个常驻进程。
我的做法是检查点快照机制。每次代码执行完成后,对运行时做一次序列化快照,把执行环境的关键状态(变量值、已导入的模块、当前工作目录)保存到对象存储。下次用户发新指令时,先加载快照,再在新的运行时环境中恢复状态,然后执行新代码。
听起来简单,实现起来坑很多。Python 的 pickle 可以序列化大多数对象,但遇到 lambda 函数、生成器、打开的文件句柄就完全白搭。我遇到过用户用 open() 打开了一个文件,然后下一次提问时试图对这个文件做操作,结果恢复状态后发现文件句柄已经失效。
最后的解决方案是半持久化:只持久化关键变量和基本类型,文件操作和 IO 对象不做恢复。这种场景下,系统会明确提示用户"文件句柄无法跨会话保留,请重新打开文件"。宁可少做一点,也不能给出错误结果误导用户。
3. 核心细节解析与实操要点
3.1 输入处理:用户代码二次加工的那些坑
用户提交的代码远没有想象中规范。有的用户只写一行 print("hello"),有的用户贴了一整段带有 Markdown 标记的代码块,还有少数用户会直接写 df.head() 这种在 Jupyter 里能自动显示结果、但在命令行里没有任何输出的语句。
针对这种情况,我在执行前增加了两道预处理:
第一道:代码清洗。从用户消息里剥离 Markdown 标记(``` 和语言标识),提取纯 Python 代码。如果检测到代码里只有表达式没有赋值和打印,会自动包一层 print()。比如 df.head() 会被自动改写成 print(df.head()),保证用户能看到输出。
第二道:安全审计。用 AST(抽象语法树)对代码做静态分析,检查是否包含危险调用。列一个黑名单:os.system、subprocess、__import__、open(特定模式下)、eval、exec 这些都在名单里。一旦命中直接拦截,不给执行机会。
注意:AST 检查只能作为第一道防线,不能作为唯一防线。攻击者可以动态构造字符串再
eval,或者用getattr绕开静态检查。所以容器隔离才是安全的核心,AST 检查只是减少无谓的容器调用。
3.2 超时控制:防止死循环把系统拖垮
while True: 是每个代码执行平台都会遇到的问题。我最初设置的是单次执行 10 秒超时,但很快就发现这两种需求的冲突:有的用户确实需要跑一份较长耗时的事务代码,比如训练一个小模型或用 pandas 处理大批量数据,10 秒根本不够;但如果不设限制,死循环和恶意代码会拖垮整个系统。
最终方案是分档超时。默认普通用户单次执行上限 30 秒,VIP/付费用户可以提到 120 秒。这个阈值的设置参考了云函数 FaaS 平台的惯例——大多数 FaaS 平台执行时间限制在 60 秒到 15 分钟之间。我对 AI 对话场景做了统计,绝大多数正常代码在 10 秒内能跑完,30 秒的配额已经覆盖了 95% 以上的场景。
超时处理的实现细节也有讲究。在容器里跑代码,超时后不能只杀掉主进程——如果代码里起了子线程或子进程,主进程被杀后它们还在跑。我用的方式是向容器内所有进程发送 SIGKILL,然后强制销毁整个容器。宁可慢一点,也不留后患。
3.3 输出捕获:不要以为只需要 print
代码执行结果不只是 print 输出的内容。实际的输出类型远比想象中丰富:
- 标准输出 vs 错误输出:执行成功但 print 要区分打印日志和报错堆栈,需要同时捕获
stdout和stderr。 - 绘图结果:用户用
matplotlib画图时,要能生成图表,不能只输出一句<Figure size 640x480 with 1 Axes>。解决办法是用matplotlib.use('Agg')设为非交互式后端,执行完成后把图片保存为 PNG,经 Base64 编码输出,前端用<img>展示。 - 表格数据:
pandasDataFrame 的显示问题。在 Jupyter 里可以用 HTML 渲染,在命令行环境下只输出纯文本格式,多种多样。我做了格式化模块,把 DataFrame 转成 HTML 表格,保证用户看得到结构化数据。
这里有一个很细节的问题,执行 Java 时 JVM 有 System.out 和 System.err 两套输出流,在 Python 里是 sys.stdout 和 sys.stderr。如果只捕获 stdout,用户代码报错时错误信息全进 stderr,前端只显示空白。最初的版本连这个都踩过,后来规范制定了"无论执行成功还是失败,都应同时捕获并展示 stdout 与 stderr"的强制约定。
3.4 代码执行引擎与 AI 的协作方式
这个项目的核心场景不是"用户写代码给电脑跑",而是"AI 写代码给电脑跑"。所以执行引擎必须和 AI 模型联动。
典型流程是:
- 用户用自然语言描述需求:"读取 data.csv,统计每一列的缺失值比例,画出柱状图"。
- AI 模型根据对话历史生成一段 Python 代码。
- 执行引擎收到代码后执行,把 stdout、图片、表格等结果返回给 AI。
- AI 根据执行结果对用户做解释和下一步规划。
这个流程里最难的是错误自动修正。用户让 AI 读取文件,AI 生成的代码可能会把文件名写错、路径拼错、列名大小写不一致。第一次执行必然报错。这时候不能让用户手动纠错,而是应该让 AI"看"到报错信息,自动修正后重新执行。
我实现的方式是加了一个小的重试循环:最多尝试 3 次,每次报错后把 stderr 内容作为上下文重新发送给 AI,要求它在原代码基础上做修正而不是重写。实测下来,这种"执行→报错→反馈→重试→执行"的循环,能把 AI 自动生成代码的一次成功率从 62% 拉高到 88% 左右。
需要注意重试次数不能设太高。每次重试都意味着额外的时间和 token 消耗。我自己测试过,超过 3 次后 AI 往往会陷入"瞎猜"的状态,与其让它继续猜,不如把报错信息交给用户,让人做决策。
4. 实操过程与核心环节实现
4.1 环境准备与依赖选型
整个系统的技术栈我采用的组合如下:
- 语言与框架:Python 3.11 + FastAPI(执行层 API 服务)
- 沙箱:Docker + 自定义容器池管理器
- 状态管理:Redis(会话状态缓存)+ PostgreSQL(元数据持久化)
- AI 能力接入:通过大模型 API 实现代码生成与自动修正
- 前端:对话框交互界面,通过 WebSocket 与后端通信,实时接收执行结果
选 FastAPI 而不是 Flask,主要看中它的异步特性和 WebSocket 原生支持。代码执行是耗时操作,需要异步处理避免阻塞其他请求。FastAPI 的 BackgroundTasks 和 asyncio 支持让并发控制省了不少事。
依赖库的规划方式是先把常用的数据科学栈装好:numpy、pandas、matplotlib、scikit-learn、requests。剩下的库用户有需求时临时 pip install 即可。这个设计参考了在线代码平台的通行做法——预装流行库提升体验,冷门库即时安装兜底。
4.2 会话与执行环境的数据模型设计
通信和状态管理方面,我的数据模型如下:
| 表名 | 主要字段 | 用途 |
|---|---|---|
conversations |
id, title, created_at, last_active_at |
会话元数据 |
messages |
id, conversation_id, role, content, created_at |
消息记录 |
execution_records |
id, message_id, status, result_json, duration_ms, created_at |
执行记录 |
session_states |
conversation_id, state_payload, state_version, updated_at |
会话状态快照 |
核心表是 execution_records。每次代码执行完成,结果会拆成大 JSON 存进去,包含 stdout、stderr、图片 Base64、执行耗时。用户刷新页面后可以回看之前的执行结果,不用重新跑一遍代码。
会话状态的快照我放在 session_states 表,这是实现"AI 记住上次执行结果"的核心。每次执行完成后做快照,把最新状态写进去。恢复时读快照,反序列化后注入新容器的运行时。快照有个 state_version 字段做版本管理,防止并发恢复时出现状态错乱。
Redis 则用作短期热点会话缓存。活跃会话的状态快照先写 Redis,设置 24 小时过期,过期后再从 PostgreSQL 冷存储恢复。这个优化让高频用户的会话恢复时间从 300ms 降到了 30ms 左右。
4.3 容器池调度与生命周期管理
容器池的管理是系统稳定性的重头戏。完整的调度流程如下:
- 收到执行请求,解析出会话 ID。
- 从容器池管理器请求一个空闲容器,标记为"已占用"。
- 如果池中无空闲容器且未达上限,拉起新容器;如果已达上限,进入排队等待。
- 加载会话快照,注入容器。
- 执行代码,收集结果。
- 执行完成,保存状态快照,容器归还池中。
容器的生命周期分四态:初始化、空闲、占用、销毁。空闲容器超过 10 分钟自动销毁释放资源。容器池下限保持 2 个,"预热"保证用户请求不需要等待容器启动。
容器的资源限制是强制配置的:--memory=1g --cpus=0.5 --pids-limit=128。内存 1GB、CPU 0.5 核、进程数上限 128。前两个参数控制资源占用,pids-limit 是为了防止用户代码 fork 出大量子进程拖垮容器。磁盘只写临时目录,且 tmpfs 挂载(内存文件系统),容器销毁即清空。
4.4 关键代码:执行引擎核心逻辑
执行引擎的核心部分,我用伪代码梳理了思路,完整的实现会比这更复杂:
python复制import ast
import asyncio
import time
from typing import Any, Dict
class ExecutionEngine:
def __init__(self, container_pool, state_manager):
self.container_pool = container_pool
self.state_manager = state_manager
self.MAX_RETRY_AI = 3
async def execute_with_ai(self, code: str, conversation_id: str, ai_client):
"""执行代码并支持 AI 自动修正"""
for attempt in range(self.MAX_RETRY_AI):
result = await self._execute(code, conversation_id)
if result["status"] == "success":
return result
# 执行失败,把错误信息交给 AI 修正
if result["stderr"]:
code = await ai_client.fix_code(
original_code=code,
error_message=result["stderr"][-2000:]
)
if not code:
break
return result
async def _execute(self, code: str, conversation_id: str) -> Dict[str, Any]:
# 1. 代码清洗 + 安全检查
cleaned_code = self._clean_code(code)
self._security_check(ast.parse(cleaned_code))
# 2. 从池中获取容器
container = await self.container_pool.acquire(timeout=10)
try:
# 3. 恢复会话状态
session_state = await self.state_manager.load(conversation_id)
# 4. 拼接执行脚本(恢复状态 + 用户代码 + 输出收集)
script = self._build_script(session_state, cleaned_code)
# 5. 执行并等待结果
start = time.time()
raw_output = await container.run_async(script, timeout=30)
duration = time.time() - start
# 6. 保存新状态
await self.state_manager.save(conversation_id, raw_output["state_payload"])
return {
"status": "success",
"stdout": raw_output["stdout"],
"stderr": raw_output["stderr"],
"duration_ms": int(duration * 1000),
"images": raw_output["images"]
}
finally:
# 7. 归还容器
await self.container_pool.release(container)
这个代码结构体现了几个关键决策:AI 修正的重试循环放在最外层;容器获取设置了超时,防止容器池枯竭时无限等待;finally 中确保容器一定会归还——即使发生了异常,容器也要归还,否则很快就会耗尽所有容器。
4.5 实际执行流程演示
这里用一个真实案例展示整个链路。用户发送消息:"读取 sales.csv,按月份统计销售额,画个折线图"。
第一步:AI 生成代码
AI 输出的代码约十几行:先用 pandas.read_csv() 读文件,再把日期列转成 datetime 类型,用 groupby 按月聚合,最后 matplotlib 画折线图并 plt.show()。
第二步:代码预处理
清洗阶段把 plt.show() 去掉(在无头环境会阻塞执行),替换为 plt.savefig() 生成图片,同时用 print() 把统计结果输出到 stdout。
第三步:执行
容器池返回空闲容器,注入上次会话状态,执行脚本。整个执行耗时约 1.2 秒。
第四步:结果返回
前端同时显示三块内容:statistics 文本信息、HTML 格式的月度销售统计表、绘制好的折线图。
第五步:AI 解释
AI 拿到执行结果后,生成一段解释"8 月销售额最高,达到 X 万元,环比增长 Y%。如果需要进一步分析,我可以帮你做同比对比"。用户基础的操作体验非常顺畅,下一步还可以让 AI 基于现有数据给出新的分析。
5. 常见问题与排查技巧实录
5.1 死循环或资源耗尽问题
现象:用户执行 while True: pass,30 秒超时后系统报警,容器一直在消耗 CPU。
排查方法:容器配置中限制 --cpus=0.5 后,即使死循环,单容器也只能占满 0.5 核,不会拖垮宿主机。配合 --pids-limit=128,进程数过多时直接报错。
最终方案:超时触发后发 SIGKILL 杀掉容器内所有进程,然后直接销毁容器,从池中换新。实测重启一个容器约 300ms,用户感知是"执行超时后重试即可"。这里不要用"优雅关闭",对于不信任的代码,直接杀掉比礼貌请求退出更安全。
5.2 AI 生成代码的路径错误
现象:AI 写代码时总把文件路径写成绝对路径,比如 /home/user/data/sales.csv,而不是用户上传后存放在平台里的相对路径 /tmp/uploads/sales.csv。
排查方法:代码执行前做路径重写。用正则匹配把 /home/、/Users/ 等常见绝对路径前缀替换为容器内的实际路径。同时给 AI 的 system prompt 里明确写上"文件统一存放在 /tmp/uploads/ 目录下,代码中只能使用相对路径或该前缀路径",从源头减少错误。
经验补充:用户上传的数据文件归属关系也要处理。如果用户 A 在会话中上传了一份 CSV,用户 B 在另一个会话中让 AI 读取同样的文件名,系统必须保证 B 读不到 A 的文件。我用的是"会话 ID + 文件名"双重维度标识文件归属,路径形如 /tmp/uploads/{conversation_id}/{filename},从根本上隔离数据。
5.3 matplotlib 中文乱码问题
现象:用户让 AI 画图,图表标题或坐标轴标签出现方块乱码。
排查过程:matplotlib 默认中文字体支持不完善,需要额外配置。容器镜像里装了 fonts-noto-cjk 字体包,并在每次执行前设置 matplotlib.rcParams['font.sans-serif'] = ['Noto Sans CJK SC']。
这个坑不算难,但很容易忽略。很多项目上线时测试数据都是英文标签,上线后用户一用中文就翻车。这里建议架构设计中就把中文字体作为镜像的基础依赖打进去,而不是等出问题再补。
5.4 并发执行时的 Python 环境冲突
现象:两个用户同时执行需要不同版本的包(比如 pandas 1.5 和 pandas 2.1),如果共享一个虚拟环境必然冲突。
解决思路:容器天然隔离环境,所以这个问题的解法在池化设计上:每个容器只服务一个会话,会话结束后容器会重置镜像。具体做法是容器被回收前执行 docker commit 保存当前环境状态(包含用户新 pip 安装的包),下次会话创建时以这个 commit 的镜像启动,实现环境"记忆"。
注意:这种方法会显著增加存储和镜像管理复杂度。我实践下来,只对长周期、高价值的会话启用环境持久化,大多数会话还是用完即焚,直接恢复镜像默认环境。
5.5 执行结果过大导致接口超时
现象:有用户跑了一个 dataframe.describe(),结果只有几百 KB,但图片和 stdout 叠加后,WebSocket 消息太大导致前端渲染卡死。更极端的情况是 AI 生成了 100 张图表,全塞进一个响应体里。
验证方案:限制单次执行结果总大小不超过 3MB。超限后对图片做压缩、对 stdout 做截断,统一提示"结果过大,已自动压缩显示。如需完整数据,请下载日志文件"。之后下载文件通道作为兜底,用户可拿到原始结果。
5.6 常见问题速查表
| 问题 | 现象 | 根因 | 解决方案 |
|---|---|---|---|
| 死循环 | 执行超时、CPU 占用高 | 用户代码死循环 | cpus/pids 限制 + SIGKILL 强杀 |
| 路径错误 | FileNotFoundError | AI 写错了路径 | 路径重写 + 明确目录规范 |
| 中文乱码 | 图表方块字 | 缺少中文字体 | 预装 Noto CJK 字体 |
| 依赖冲突 | ModuleNotFoundError | 会话间环境不隔离 | 容器隔离 + 环境快照 |
| 结果过大 | 前端卡死 | 输出数据量超限 | 结果限流 + 图片压缩 |
| 文件访问越权 | 安全问题 | 路径未做归属隔离 | 会话目录隔离 |
6. 架构优化的进阶方向
6.1 从同步执行到异步任务队列
当前架构是同步执行——用户发代码,等结果返回,整个过程 HTTP 请求保持连接。在低并发下没问题,但高峰时期大量执行请求挤在一起,WebSocket 连接数会打满。
我规划的升级方案是引入异步任务队列:用户提交代码后立即返回 task_id,后端把任务投递到消息队列(比如 Redis Stream 或 RabbitMQ),执行器从队列消费任务执行,结果通过 WebSocket 推送给前端。这样 Web 服务层不直接依赖代码执行耗时,伸缩性会好很多。
这个改造涉及前端交互方式的调整:从"请求-响应"模式变成"请求-轮询/推送"模式。用户侧感知差别不大,但服务端可以更从容地应对大流量和并发。执行任务的编排也更容易——比如超时重试、失败补偿、执行日志收集,这些都可以在队列消费者层面统一处理。
6.2 会话恢复的优化方向
现在的快照机制主要覆盖变量状态,文件系统状态恢复做得很粗糙。我的规划是引入类似 Jupyter 的 .ipynb 文件机制——把执行历史完整记录下来,用户可以选择从任意一个历史节点"分支"继续,而不是只能从最新状态继续。
这个功能在调试场景很实用。用户在分析数据时跑出了中间结果,想保留当前结果、但改一段后续代码再跑一次——如果只有"最新状态"一个节点,就需要手动保存中间变量或者重跑一遍全流程,很浪费。完整的执行历史记录加分支执行能力,能极大提升对话式编程的体验。
6.3 用 LLM 自动生成测试用例
另一个我觉得价值很高的方向是:利用 LLM 为用户的代码自动生成测试用例并执行。用户写完一段处理逻辑后,系统自动生成 3-5 个边界测试用例,在沙箱里跑一遍,把结果直接反馈给用户。
比如用户写了一个 calculate_discount(price, discount_rate) 函数,系统自动测试:discount_rate = 0、= 1、= 0.5、= -0.1(非法输入)、price = 0 等边界情况。然后告诉用户:"你的函数在 discount_rate 为负数时没有做校验,可能导致折扣为负。"这种体验比单纯执行代码更有价值,能让 AI 从"帮你写代码"进化到"帮你验证代码"。
7. 写在最后的实操心得
整个项目做下来,我最想强调的一点是:AI 对话框里的代码执行,本质上和传统的代码执行平台共享同一个核心——它们都是代码运行的沙箱。但 AI 场景多了两个额外的复杂度维度:代码是自动生成的,不保证正确;执行结果还要被 AI 理解,用于继续生成代码。这两个特性决定了架构设计的重心:你不能只追求执行性能,更要追求"错了能自动修"的能力,以及"执行结果结构化、可被模型理解"的输出标准。
另一个心得是资源控制的取舍。在小规模场景下,容器池 + 超时 + 资源限制这套方案完全够用。真正的大规模并发场景可能需要更复杂的 Kubernetes 集群管理,但那种复杂度对小团队来说是负担而不是帮助。架构设计的艺术在于:在满足当前需求的前提下留出扩展空间,而不是一上来就搞一套过度设计。
最后再分享一个小技巧:一定要在容器的 entrypoint 脚本里加上 trap 信号处理。容器被销毁时,Docker 会发 SIGTERM 给 PID 1,如果你的入口脚本不做信号处理,被强杀时有概率产生僵尸进程,把宿主机 /proc 表占满。这个细节我在生产环境踩过坑,加了 trap 'exit 0' SIGTERM 之后问题就再没出现。
如果你正在做类似的 AI 代码执行功能,希望这篇文档能帮你少走一些弯路。核心思路就一句话:把对话状态、执行沙箱、AI 生成和纠错能力分开设计,再通过清晰的接口串联起来。想清楚这一点,整个系统的复杂度就化解了一大半。
