许多 Agent 项目从一段看似清晰的业务代码开始:先让大模型拆解任务,再根据返回文本调用搜索、图片或视频接口,最后把所有结果交给另一个模型汇总。原型阶段只有三五个节点,这种写法足够直接;进入生产环境后,节点数量、模型版本、租户策略和失败分支同时增长,代码很快变成一张由 Prompt、条件语句和供应商 SDK 拼成的隐形流程图。
开发者无法在执行前回答三个基本问题:这次任务最多会花费多少 Token,会产生哪些外部副作用,任一节点失败后系统将从哪里恢复。
解决这些问题不能继续堆叠 Prompt Engineering。更合适的抽象是把 Agentic Workflow 看成一门领域语言,把用户目标编译成带类型、预算、依赖和验收条件的中间表示,再交给多模型运行时执行。模型只是编译后端之一,检索器、数据库、图像引擎和视频队列也是后端。API Gateway 则类似驱动层:它屏蔽协议差异,却不负责决定业务语义。
本文以“工作流编译器”为主线,构建一套从 Agent IR、静态检查、Token 成本规划、异步运行时、统一协议驱动到差分回放的完整方法。它与常见的“调用哪个模型”教程不同,重点不是某个模型的单次回答,而是如何让一条包含 DeepSeek、Claude、GPT、Flux、Midjourney、Kling 等异构能力的执行计划可验证、可预算、可中止并且可重放。
一、为什么 Prompt Chain 会失控:Agent 需要编译阶段
1. 解释执行的三个盲区
传统 Chain 本质上是解释执行:上一步生成一段自然语言,宿主程序读取这段文字,再决定下一步。问题在于,自然语言同时承担了数据、指令和控制流三种职责。模型写出“继续搜索”究竟是一个普通句子,还是一次工具调用?返回“已完成”是否意味着结果通过了验收?只依赖字符串约定,系统无法在运行前做静态分析。
第二个盲区是成本不可闭合。规划器可以动态追加步骤,一个看似简单的任务可能在反思循环中调用十几次模型。每次请求都没有错,但总成本已经超过业务价值。
第三个盲区是副作用不可追踪。发送通知、修改数据库、提交视频任务都可能在超时后实际成功;如果执行器把超时等同于失败并直接重试,就会制造重复操作。
编译式 Agent 将“决定做什么”和“真正执行”分开。前端解析用户目标,编译器输出结构化任务图,静态检查器拒绝不完整或越界的计划,优化器进行路由和缓存改写,运行时只执行已经签名的图。模型仍可参与规划,但其输出先被当作候选程序,而不是可以立即执行的命令。
LangChain、LlamaIndex、AutoGPT 等框架可以继续存在于这套体系中,但它们更适合作为前端 DSL、工具封装或检索组件,而不是整个生产运行时。框架生成的链路先转换为统一 IR,再接入组织自己的权限、预算和审计策略。这样既保留框架的开发效率,也避免业务流程被某个框架的消息对象、回调方式或版本升级锁定。
2. 编译器与运行时的职责边界
一条完整链路可以表达为:
自然语言目标 | v Intent Parser -> Agent IR -> Static Analyzer -> Plan Optimizer | v Signed Execution Plan | v Event Runtime -> Model/Tool Drivers -> Validators | | | +---------- Result Ledger <-------+编译器负责确定节点类型、输入来源、依赖关系、预算上限、截止时间、可重试性和验收器;运行时负责排队、并发、取消、租约、状态持久化与事件投递;驱动层负责把统一调用转换为 OpenAI Compatible Protocol 或供应商原生协议;验证器则判断结果能否进入后继节点。
这种分工带来一个重要变化:业务评审可以发生在调用模型之前。安全团队能够检查计划中是否包含高风险工具,FinOps 能够检查成本上界,平台团队能够确认候选能力是否存在。计划未通过时,系统不会产生任何上游费用或外部副作用。
编译结果还可以缓存,但缓存对象不是最终答案,而是经过检查的计划模板。只要用户约束、策略版本、工具版本和能力目录未变化,同类目标可以复用拓扑结构,只重新绑定输入资产与截止时间。
策略或工具 Schema 更新后,相关计划模板立即失效。这种“计划缓存”减少规划 Token,同时不会把旧答案误当成当前事实。
3. 确定性外壳与概率性内核
LLM 输出具有概率性,但包围它的系统不必同样随机。节点输入、Schema、超时、重试次数、预算和状态转换都可以确定;只有“模型生成内容”这一小段保持概率性。工程目标不是消除概率,而是把它限制在可测量的边界内。
例如脚本节点允许多种措辞,但必须返回三段式结构、目标受众和镜头数量;图片节点允许画面差异,但必须满足宽高比、资产格式和安全策略。确定性外壳使自动化测试不必比较整段文本是否相同,只需验证不变量是否成立。
二、设计 Agent IR:让任务、资产和副作用拥有类型
1. 中间表示不是聊天记录
Agent IR(Intermediate Representation)不是模型消息的简单封装。它描述的是一个可执行节点:需要哪些输入资产,产生何种输出,调用什么能力,消耗多少预算,失败时能否重试,以及完成后由谁验收。
聊天记录只是某些文本节点的输入之一,不应成为整个系统的状态容器。
最小节点可以包含以下字段:
| 字段 | 作用 | 静态检查示例 |
|---|---|---|
node_id | 图内唯一标识 | 不得重复 |
capability | 能力别名 | 必须存在于能力目录 |
inputs | 上游资产引用 | 引用节点必须先完成 |
output_type | 输出类型 | 必须与后继输入兼容 |
budget | 成本与 Token 上限 | 所有节点之和不得越界 |
deadline_ms | 节点截止时间 | 不得超过流程截止时间 |
effect | 副作用等级 | 高风险操作要求审批 |
validator | 验收规则 | 发布前节点不得为空 |
2. 使用 Pydantic 固化契约
下面的类型只表达编译期信息,不包含任何供应商 SDK 对象。Capability是稳定别名,真实模型 ID 在部署期解析。
fromenumimportEnumfromtypingimportAny,LiteralfrompydanticimportBaseModel,Field,model_validatorclassEffect(str,Enum):PURE="pure"REVERSIBLE="reversible"IRREVERSIBLE="irreversible"classAssetRef(BaseModel):producer:strname:strmedia_type:strclassNodeIR(BaseModel):node_id:strcapability:strinputs:list[AssetRef]=Field(default_factory=list)output_type:strmax_input_tokens:int=Field(ge=0)max_output_tokens:int=Field(ge=0)deadline_ms:int=Field(ge=100)effect:Effect=Effect.PURE retry_limit:int=Field(default=1,ge=0,le=5)validator:dict[str,Any]classWorkflowIR(BaseModel):version:Literal["agent-ir/v1"]workflow_id:strnodes:list[NodeIR]total_budget:float=Field(gt=0)@model_validator(mode="after")defvalidate_graph(self)->"WorkflowIR":ids=[node.node_idfornodeinself.nodes]iflen(ids)!=len(set(ids)):raiseValueError("duplicate_node_id")known=set(ids)fornodeinself.nodes:forrefinnode.inputs:ifref.producernotinknown:raiseValueError(f"unknown_producer:{ref.producer}")returnself这段定义没有检查环路、数据类型和拓扑顺序,这些工作应由独立静态分析器完成。Pydantic 负责验证数据形状,编译器负责验证程序语义,两者不能混为一层。否则复杂规则会散落在字段校验回调中,既难测试,也无法给出清晰的诊断位置。
3. 多模态资产必须通过引用传递
文本可以短暂内联,图像、音频和视频则应进入版本化资产仓库。IR 只传递asset_id、内容哈希、媒体类型、尺寸、创建节点和访问策略。这样既能避免消息体膨胀,也能保证重放时拿到同一份输入。
资产不可只用文件名寻址。同一分镜重试两次可能产生两个同名文件,如果后一次覆盖前一次,验证报告就无法与原结果对应。
内容寻址可以使用sha256或对象存储版本号;派生资产还要记录父资产与转换参数,从而形成可追踪的 lineage。
4. 把副作用写进类型系统
纯节点可以安全重算,例如摘要、分类和向量化;可逆节点需要补偿动作,例如创建临时资源后可以删除;不可逆节点则必须在执行前取得显式许可,并使用幂等收据。
编译器发现一个不可逆节点位于未验证的生成节点之后时,可以自动插入审核屏障。这类似数据库事务中的写集合分析。Agent 不再等到线上发生重复发送后才增加补丁,而是在计划阶段就知道哪条边跨越了副作用边界。
5. 能力符号表
传统编译器使用符号表解析变量与函数,Agent 编译器也需要一份能力符号表。每个能力条目声明输入类型、输出类型、最大上下文、同步或异步模式、是否支持流式、允许的数据区域、价格版本和质量等级。符号表只描述逻辑能力,不暴露真实凭证。
同一个reasoning.plan可以关联多个实现,但编译器看到的是稳定函数签名。若某个后端停止支持工具调用,平台只修改该实现的能力声明,静态分析器会自动阻止需要工具调用的计划绑定到它。
相比在业务代码里维护模型白名单,这种类型化目录更容易测试,也能在部署前发现不兼容变更。
三、从目标到可执行图:工作流编译器的四遍处理
1. 第一遍:语义解析与约束提取
编译器前端先把用户目标拆成事实、偏好、硬约束和待确认项。比如“生成一段 30 秒中文产品视频,不能出现价格,明天下午前完成”至少包含模态、时长、语言、禁用字段和截止时间。解析器必须用结构化 Schema 返回,缺失的关键参数标为unknown,不能自行猜测。
对高风险工作流,解析结果要显示给调用方确认;对低风险任务,可以根据租户默认策略补全。所有补全都记录来源:用户输入、项目配置、组织策略或编译器推导。后续发生冲突时,优先级由来源而不是文本出现顺序决定。
2. 第二遍:类型检查与依赖分析
静态分析器构建有向图,检查环路、孤立节点、不可达分支、输入输出类型和截止时间传播。若video.compose需要分镜图列表,而上游返回普通文本,计划应在执行前失败。若两个节点都修改同一外部对象,分析器应标记写冲突,要求串行或增加版本条件。
截止时间采用反向传播:最终交付节点的 deadline 向前减去各节点的 P95 运行时间与安全余量。如果某个前置节点得到的可用时间为负,说明这张图在当前 SLO 下不可行。
系统应该缩减任务、改用低延迟能力或转为异步交付,而不是让请求运行到一半才超时。
3. 第三遍:计划优化
工作流优化与传统编译器有相似之处。Semantic Cache 可以被视为公共子表达式消除:当两个纯节点拥有相同规范化输入、知识快照、模型策略和输出 Schema 时,只需执行一次。
Prompt 中不影响语义的空白和字段顺序可在计算缓存键前规范化,但时间、租户权限和随机种子必须进入键空间。
独立节点可以并行化;小型同类节点可以批处理;连续的格式转换可以合并;永远不会被消费的中间结果可以删除。不过,模型调用不是完全透明的函数。只要节点依赖当前时间、外部检索、个性化数据或随机生成,优化器就必须保守。
每次改写都写入优化日志,保留改写前后的 IR,便于解释成本为何发生变化。
4. 第四遍:路由绑定与计划签名
编译前端只指定能力,例如reasoning.plan、vision.storyboard、video.compose。最后一遍根据区域、租户权限、上下文长度、价格版本和近期质量,把能力绑定到候选池,而不是单一模型。
运行时仍可在候选池内做健康切换,但不能越过编译器设定的质量与合规边界。
计划完成后计算哈希并签名。运行时收到的每个节点都携带plan_hash,任何动态追加节点都必须生成新版本并重新通过检查。这能阻止执行器或模型在未审计的情况下扩大权限。
签名不是为了证明模型输出正确,而是为了证明实际执行的程序与已审核计划一致。
5. 策略即代码的静态分析
企业规则不应散落在 Prompt 中,例如“客户数据不得离开指定区域”“生产发布必须人工确认”“单次视频任务不得超过预算上限”。
这些规则应编写为针对 IR 的可测试策略,在编译阶段返回允许、拒绝或要求补充条件。诊断信息必须指向具体节点和字段,而不是只返回“策略不通过”。
策略之间还可能冲突:低延迟策略要求跨区域后端,数据策略却禁止出区;低成本策略选择轻量模型,质量策略又要求高置信度。
编译器应按照硬约束优先、软目标优化的方式求解,并在无可行解时明确列出冲突集合。沉默地忽略某条规则会让系统表面成功,却破坏治理边界。
6. 一个简单的拓扑编译器
fromcollectionsimportdefaultdict,dequefromdataclassesimportdataclass@dataclass(frozen=True)classStep:name:strdeps:tuple[str,...]p95_ms:intdefcompile_levels(steps:list[Step])->list[list[str]]:by_name={step.name:stepforstepinsteps}indegree={step.name:0forstepinsteps}children:dict[str,list[str]]=defaultdict(list)forstepinsteps:fordepinstep.deps:ifdepnotinby_name:raiseValueError(f"unknown_dependency:{dep}")indegree[step.name]+=1children[dep].append(step.name)ready=deque(nameforname,degreeinindegree.items()ifdegree==0)levels:list[list[str]]=[]visited=0whileready:current=list(ready)ready.clear()levels.append(current)fornameincurrent:visited+=1forchildinchildren[name]:indegree[child]-=1ifindegree[child]==0:ready.append(child)ifvisited!=len(steps):raiseValueError("cycle_detected")returnlevelslevels中同一层的节点具备并行执行条件,但运行时还要结合并发额度、内存、租户配额和上游频控做二次调度。
编译器证明“可以并行”,运行时决定“此刻是否应该并行”。
四、把 Token 当作计算资源:成本模型、准入控制与路由
1. 从请求计费转向计划计费
企业通常按单次请求统计模型成本,但 Agent 的真实计费单元应是一张执行计划。一次用户请求可能触发规划、检索、脚本生成、图像理解和多轮修复。若只看最终模型,最昂贵的循环会隐藏在中间节点。
设节点i的输入、输出 Token 上限分别为I_i、O_i,对应单价为p_i、q_i,重试上限为r_i,工具与资产成本为a_i,则保守成本上界为:
[
C_{max}=\sum_{i=1}^{n}(1+r_i)(p_i I_i+q_i O_i+a_i)
]
这个上界通常高于实际成本,但它适合做准入控制。优化器还可以根据历史分布给出 P50、P95 预测区间。业务策略分别限制硬上界和期望值:前者防止灾难性循环,后者用于日常预算管理。
成本账本需要区分预留、实际消耗与释放。任务被接纳时先预留预算,节点完成后以真实 usage 结算,未使用部分立即归还。异步任务长时间排队时,预留额度不能无限占用,可设置租约并在过期后重新评估。
价格表本身带版本号,因为同一计划在不同日期执行可能使用不同单价;审计报表必须按当时生效版本复算,而不是套用今天的价格。
2. 准入控制先于 Load Balancing
传统 Load Balancing 关注把流量均匀分给健康实例,模型系统则必须先判断任务能否被接受。准入器检查租户余额、并发槽位、截止时间、数据区域、能力可用性和预测成本。任何一项违反硬约束,都不能靠换一个后端掩盖。
对于长视频等异步任务,准入成功只意味着计划进入队列,不意味着同步请求会等待完成。系统返回任务句柄和预计阶段,客户端通过事件或轮询获取进度。
这样可以把 HTTP 生命周期与生成生命周期解耦,避免连接超时后任务仍在上游运行。
3. Token Dynamic Routing 的多目标决策
通过准入后,Token Dynamic Routing 才对候选后端排序。评分不仅看价格,还要考虑质量预测、排队时间、TTFT、错误率、上下文利用率和切换风险:
[
U(m,t)=w_qQ(m,t)-w_cC(m,t)-w_lL(m,t)-w_eE(m,t)-w_dD(m,t)
]
其中Q是任务条件下的质量概率,C是成本,L是预计延迟,E是错误风险,D是相对当前路由的漂移惩罚。先用硬条件过滤,再最大化效用函数。
权重按任务类型配置:代码修复提高质量权重,交互式分类提高首字延迟权重,批量离线摘要提高成本权重。
路由决策还要设置滞回窗口。若两个候选分数接近,保持当前路由通常比频繁切换更稳定。只有替代路由连续多个窗口显著领先,才逐步增加权重。
每次选择都记录候选集合、特征快照和route_reason,让成本审计能够回答“为什么这次没有使用更便宜的模型”。
实时调度还必须考虑队列公平性。若一个大租户提交大量离线摘要,不应挤占小租户的交互请求;同一租户内部,高优先级任务也不能永久饿死普通任务。
可以按租户设置加权公平队列,再在租户内部按 deadline 和优先级排序。队列等待时间一旦侵蚀节点预算,调度器需要重新绑定低延迟候选或拒绝任务,不能沿用入队时已经过期的路由判断。
4. 模型名称只应是可变后端
DeepSeek-R1、DeepSeek-V3、Claude 3.5 Sonnet、GPT-4o、Qwen2.5-72B、Llama-3.3 可以进入文本能力候选池;Flux.1 Dev/Pro、Midjourney V6 API 可以进入图像后端;Kling V1.5/V3 API、MiniMax Video-01 和 Sora API 映射可作为视频能力配置。
这些名称不应成为代码常量,也不应被理解为当前平台必然提供的固定 ID。诸如 GPT-5.6、Qwen 3.7 Max 等字符串若出现在内部配置中,应明确标记为路由别名,由部署环境映射到当时实际可用的供应商模型。
可用性、价格和版本都以运行时配置为准。
5. Benchmark 必须服务于路由而非排行榜
下面是一张示例评测表,用于说明编译器需要哪些输入。数值是合成工作负载的演示值,不是供应商性能声明。
| 能力池 | 样本任务 | TTFT P50 | 总延迟 P95 | Schema 通过率 | 任务成功率 | 成本指数 |
|---|---|---|---|---|---|---|
reasoning.deep | 多约束计划 | 1.8 s | 13.2 s | 97.1% | 94.8% | 1.00 |
reasoning.fast | 分类与改写 | 0.6 s | 4.4 s | 93.0% | 96.1% | 0.29 |
vision.scene | 分镜约束抽取 | 1.3 s | 8.1 s | 95.2% | 92.7% | 0.61 |
image.render | 统一风格图像 | 2.5 s | 20.6 s | 89.4% | 87.9% | 0.78 |
video.compose | 短视频生成 | 4.6 s | 58.0 s | 84.3% | 81.8% | 2.55 |
测试集必须版本化,并按输入长度、语言、工具数量、模态和风险等级分层。报告中位数、P95、置信区间与失败类型,而不是只给平均值。
供应商更新模型或安全策略后,旧分数自动过期;新后端先跑影子流量,达到质量门槛后才进入生产候选池。
五、运行时语义:协程、事件账本与补偿事务
1. Asyncio 不是运行时的全部
Python Asyncio 能高效承载网络 I/O,但asyncio.gather并不等于一个可靠的 Agent 调度器。运行时还要处理持久队列、租约、优先级、截止时间、取消传播、分布式限流和进程崩溃恢复。
协程只存在于当前进程内,一旦 worker 退出,内存中的 Future 不具备恢复能力。
正确做法是把数据库或事件流作为事实来源。worker 领取节点时写入带过期时间的租约,执行期间续约;进程失联后,其他 worker 只有在租约过期时才能接管。
节点状态更新携带版本号,通过乐观锁防止回调、超时处理器和恢复线程同时覆盖结果。
重试策略必须接受“剩余时间”和“剩余预算”两个输入。固定重试三次看似稳妥,却会在上游故障时造成重试放大:所有 worker 同时等待、同时醒来、再同时冲击后端。
运行时应采用带随机扰动的指数退避,尊重上游retry_after,并为每个能力设置全局重试额度。当错误率越过断路阈值时,新任务停止进入故障驱动,少量探针用于判断恢复状态。
2. 事件账本而不是最终状态表
只保存status=FAILED无法解释失败过程。事件账本按时间追加NODE_ADMITTED、ROUTE_BOUND、REQUEST_SENT、FIRST_TOKEN、ASSET_STORED、VALIDATION_FAILED、RETRY_SCHEDULED和NODE_COMMITTED。当前状态由事件折叠得到,原始事件不可修改。
这种 Event Sourcing 设计允许重建任意时间点的状态,也能计算排队、网络、推理、验证分别花了多少时间。
日志中不直接保存敏感 Prompt,而是保存脱敏摘要、内容哈希和加密资产引用。观测系统通过trace_id将网关请求、上游 request ID、工具执行和资产写入串成一条链路。
3. 用 Saga 管理跨系统副作用
Agent 很难使用传统数据库的全局事务:模型服务、对象存储、消息系统和视频队列不会参加同一个两阶段提交。更现实的方式是 Saga。
每个可逆节点声明补偿动作,例如创建临时素材的补偿是删除素材,写入草稿的补偿是恢复旧版本。
补偿不是简单倒序重试。系统先判断哪些步骤已经提交、哪些补偿仍然合法,再根据依赖关系执行。不可逆节点没有真正的补偿,只能通过审批屏障、幂等键和执行回执降低风险。
若外部调用超时但结果未知,节点进入UNCERTAIN,先查询回执,不能立即重复提交。
4. Memory 应成为显式状态输入
短期对话、长期知识和工作流中间状态必须分离。长期 Memory 写入前经过来源校验、去重、脱敏、租户隔离和过期设置;读取后以带来源的资产进入 IR,而不是偷偷拼接到系统提示词。
这样编译器可以计算它占用的上下文预算,也能在回放时恢复同一版本。
上下文压缩同样属于编译优化。系统按照约束优先级、相关度、可信度和时间衰减选择内容,压缩后执行“约束存活检查”。
若禁止事项、输出 Schema 或用户已确认选择丢失,编译器必须重新调整,而不是让模型在缺少关键条件的上下文中继续执行。
Memory 的写回也要通过验证。模型在一次任务中推断出的临时结论不能自动升级为长期事实;只有带可靠来源、通过一致性检查并满足保留策略的信息才能进入长期存储。
写回事件保存来源任务、证据资产和置信度,后续事实被更正时可以按 lineage 定位并使相关记忆失效。否则错误会随着每次检索不断自我强化。
5. 取消必须沿任务图传播
用户取消根任务时,运行时先阻止新节点入队,再取消没有副作用的在途请求,最后处理已经提交的外部任务。
取消结果分为CANCELLED、TOO_LATE和COMPENSATING,不能用一个布尔值覆盖。对于不支持取消的视频后端,系统记录孤儿任务,完成后按策略归档或删除资产,避免资源长期泄漏。
六、后端驱动层:用统一网关隔离协议碎片
1. 网关是驱动层,不是业务大脑
不同模型接口在消息字段、工具调用、流式事件、错误结构和计费明细上存在差异。文本接口通常同步或 SSE 返回,图像接口可能直接返回资产,视频接口往往只返回任务句柄。
如果编译器理解这些私有格式,后端替换就会影响计划语义。
统一网关只做五件事:校验内部请求、解析租户凭证、把能力别名映射为驱动、转换协议、归一化响应与错误。业务层的预算审批、工作流依赖和内容验收不应放进网关。
这样的边界类似操作系统调用与设备驱动:上层声明意图,驱动处理具体设备差异。
在协议联调环境中,可以将 OpenAI Compatible Protocol 的统一接口测试上游配置为base_url="https://178.nz/bo",凭证从密钥服务注入;这一地址仅承担请求转换、SSE 解析、错误映射与 Token 计量的沙盒验证,不作为代码中的固定生产依赖。
2. 统一请求信封
请求信封至少包含request_id、tenant_id、plan_hash、node_id、capability、deadline_at、idempotency_key、budget、content和response_schema。
网关收到请求后先校验截止时间与幂等键,再解析候选驱动。所有上游凭证都由网关持有,Agent 运行时只使用短时内部身份。
错误被归一为INVALID_REQUEST、AUTH_FAILED、RATE_LIMITED、UPSTREAM_TIMEOUT、SAFETY_REJECTED、CONTEXT_OVERFLOW和UNKNOWN_RESULT。
只有RATE_LIMITED与部分超时适合自动重试;参数错误、安全拒绝和上下文越界需要回到编译器或业务策略处理。
3. FastAPI 驱动入口
importasyncioimportosfromdatetimeimportdatetime,timezonefromtypingimportAnyimporthttpxfromfastapiimportFastAPI,Header,HTTPExceptionfrompydanticimportBaseModel,Field app=FastAPI()classEnvelope(BaseModel):plan_hash:strnode_id:strcapability:strdeadline_at:datetime idempotency_key:strbudget:float=Field(gt=0)content:list[dict[str,Any]]DRIVERS={"reasoning.plan":os.environ["REASONING_DRIVER_URL"],"vision.storyboard":os.environ["VISION_DRIVER_URL"],"video.compose":os.environ["VIDEO_DRIVER_URL"],}defremaining_seconds(deadline:datetime)->float:now=datetime.now(timezone.utc)returnmax((deadline-now).total_seconds(),0.0)asyncdefinvoke_driver(url:str,body:dict[str,Any],timeout:float):limits=httpx.Limits(max_connections=200,max_keepalive_connections=40)asyncwithhttpx.AsyncClient(limits=limits,http2=True)asclient:response=awaitclient.post(url,json=body,timeout=timeout)ifresponse.status_code==429:raiseRuntimeError("RATE_LIMITED")response.raise_for_status()returnresponse.json()@app.post("/internal/v1/invoke")asyncdefinvoke(envelope:Envelope,x_tenant_id:str=Header()):url=DRIVERS.get(envelope.capability)ifurlisNone:raiseHTTPException(400,"UNSUPPORTED_CAPABILITY")remaining=remaining_seconds(envelope.deadline_at)ifremaining<=0:raiseHTTPException(408,"DEADLINE_EXCEEDED")payload={"messages":envelope.content,"metadata":{"tenant_id":x_tenant_id,"plan_hash":envelope.plan_hash,"node_id":envelope.node_id,"idempotency_key":(envelope.idempotency_key),},}try:result=awaitasyncio.wait_for(invoke_driver(url,payload,remaining),timeout=remaining,)exceptasyncio.TimeoutErrorasexc:raiseHTTPException(504,"UPSTREAM_TIMEOUT")fromexcexceptRuntimeErrorasexc:raiseHTTPException(503,str(exc))fromexcreturn{"node_id":envelope.node_id,"capability":envelope.capability,"result":result,}示例中的环境变量由部署平台提供,因此源码不包含供应商域名与凭证。生产实现还要加入 Secret Store、持久化幂等表、租户配额、流式转发和审计事件。
对于 SSE,网关应把私有增量字段转换为统一事件序列,并在客户端断开时向上游传播取消信号。
流式转发需要独立的背压策略。如果客户端读取速度低于模型输出速度,网关不能无限缓存 delta;它应限制缓冲区,在超限时暂停读取、降采样非关键事件或终止连接。
工具调用事件必须完整保留调用 ID 和参数片段,不能像普通文本一样丢弃。流结束后,网关发送带 usage、完成原因和资产引用的终止事件,运行时只有收到终止事件才提交节点。
4. 限流必须是多维的
单个 RPS 计数器无法描述模型资源。系统需要同时约束请求数、输入 Token、输出 Token、在途连接、异步任务数和资产带宽。
限流键至少包含租户与能力,必要时再叠加供应商和区域。不同维度使用独立令牌桶,任何一个桶耗尽都返回明确的retry_after。
网关还要实现背压。当驱动队列持续增长时,低优先级任务延迟入队,交互请求保留有限槽位,批处理任务转移到离线队列。
背压的目的不是让所有请求都成功,而是让系统在过载时保持可预测的失败边界。
七、差分回放与故障注入:验证整个系统而不是单次回答
1. 评测对象应是执行计划
单模型 Benchmark 无法覆盖 Agent 级风险。完整评测需要记录计划是否编译成功、静态检查是否发现非法副作用、实际成本是否落在预测区间、路由是否满足 SLO、失败后是否正确恢复,以及最终资产是否通过验收。
一个模型分数很高,但总是触发格式修复,它在工作流层可能更慢、更贵。
评测数据分为三层:节点层统计质量、Token 和延迟;图层统计关键路径、并行收益、重试放大与补偿次数;业务层统计最终任务通过率和人工接管率。
三层使用同一个run_id关联,才能判断问题来自模型、调度还是计划结构。
质量指标必须与失败成本对应。对于内部草稿,格式错误后自动修复可能可以接受;对于对外发布或数据库写入,同样的错误率可能完全不可接受。
评测报告因此要按副作用等级分桶,并额外统计“错误穿透验证器”的比例。一个总分很高但偶尔越过安全边界的版本,不能被平均质量掩盖。
2. 差分回放隔离变量
保存 Agent IR、模板版本、能力目录快照、资产哈希、路由特征和验收结果后,可以对历史任务进行差分回放。
一次实验只替换一个变量:候选模型、缓存策略、剪裁算法或路由权重。新旧结果由同一验证器评分,高风险样本再进行盲审。
回放不能无条件复用外部副作用。纯节点在隔离环境中重算,可逆节点使用测试资源,不可逆节点由模拟驱动代替。
涉及个人数据的样本先脱敏,并限制保留时间与访问范围。评测集本身也要防止被 Prompt 调优污染,保留从未公开的隔离集。
差分结果不仅比较最终文本,还比较计划形状。新编译器是否增加了节点、扩大了权限、改变了关键路径或引入更多不可逆操作,都应单独展示。
即使最终结果相似,计划成本和风险也可能已经发生结构性变化。对存在随机性的生成节点,可以比较约束满足率、资产属性和人工盲审分布,而不是要求像素或句子完全一致。
3. 必做的故障注入清单
第一类是网络故障:注入 DNS 延迟、连接超时、SSE 半途断开和 429,检查 deadline、退避和取消是否正确。
第二类是协议故障:返回空字段、未知事件、重复 delta 和错误 Token 统计,检查驱动能否归一化或拒绝。
第三类是运行时故障:执行中杀死 worker、延迟回调、重复投递事件,检查租约和幂等是否阻止重复副作用。
第四类是模型质量故障:让规划节点产生环路、让脚本节点遗漏硬约束、让图片节点返回错误媒体类型。系统应该在对应验证边界停止,而不是让错误一路传播到最终交付。
第五类是预算故障:模拟输出失控或反思循环,确认硬上限能中断流程并留下完整账本。
4. 一个多模态计划如何被编译执行
以“根据产品资料生成短视频草案”为例,前端首先解析语言、时长、画面比例、禁用信息和截止时间;编译器生成资料抽取、事实核验、脚本编写、镜头规划、分镜生成、视频合成和最终检查七类节点。
事实抽取与品牌约束提取可以并行,脚本必须等待二者完成,多个分镜节点可以并发,视频合成必须等待全部分镜通过。
文本节点根据复杂度进入reasoning.plan或reasoning.fast候选池,图像节点进入vision.storyboard,视频节点进入video.compose。
运行时不关心后端最终映射到 DeepSeek-R1、Claude 3.5 Sonnet、GPT-4o、Flux.1 Pro、Midjourney V6 还是 Kling V3 API,它只执行签名计划并验证统一响应。
若第二个分镜失败,系统只重算该分镜及其后继,不重做已经通过的资料抽取和脚本。若视频后端排队超过 deadline,运行时返回异步句柄或按策略终止,不占用同步连接。
若最终检查发现字幕超长,则产生一个局部修复版本,而不是让规划器重新生成整张任务图。
这正是编译式架构相较自由循环的主要收益:失败范围有限,重算范围明确,成本能够解释。
5. 上线门槛
一个候选版本只有同时满足以下条件才应扩大流量:编译成功率不下降,静态检查没有新增漏报,P95 计划成本处于预算内,关键路径延迟满足 SLO,幂等与补偿演练通过,人工接管率没有异常上升。
模型质量只是其中一项,不应覆盖运行时可靠性。
灰度期间保留旧编译器与旧能力目录,计划版本与运行时版本分别记录。发现异常时,新任务回到旧版本,已经执行的任务继续由兼容运行时完成,避免一半使用旧语义、一半使用新语义。
八、从 LLM Middleware 到 AI 编译栈:未来系统的真正边界
多模型 Agent 的长期形态不会只是更多 SDK 的集合,而会形成类似编译器与操作系统的分层。前端负责理解目标,Agent IR 固化任务语义,静态分析器检查风险,优化器处理缓存、并行和路由,运行时管理资源与失败,驱动层适配持续变化的模型协议。
模型升级因此不再要求改写整条业务链路。
这套架构的价值也不在于让所有任务完全自治。它首先让每次自治有边界:执行前知道计划,执行中看得见预算,失败后能够恢复,产生副作用时可以追踪。
只要中间表示、事件账本和验证契约保持稳定,底层模型、价格与供应商都可以替换。
已有系统不必一次性重写。第一阶段先为现有 Chain 增加统一事件和成本账本;第二阶段把工具输入输出改造成类型化资产;第三阶段引入只读静态检查,对现有计划给出诊断但不阻断;最后再启用签名计划与编译期准入。
渐进迁移能保留业务交付节奏,也能用真实运行数据校准编译器规则。
下一阶段的竞争重点将从“谁接入的模型多”转向“谁能把不确定模型变成确定工程系统”。工作流编译器不是为 Prompt 增加一层复杂包装,而是把 Agent 从试验脚本推进到可测试、可审计、可演进的软件基础设施。