不再手写 Prompt 链:用工作流编译器构建可验证的多模型 Agent 运行时

许多 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. 编译器与运行时的职责边界

一条完整链路可以表达为:

text 复制代码
自然语言目标
    |
    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 在部署期解析。

python 复制代码
from enum import Enum
from typing import Any, Literal

from pydantic import BaseModel, Field, model_validator


class Effect(str, Enum):
    PURE = "pure"
    REVERSIBLE = "reversible"
    IRREVERSIBLE = "irreversible"


class AssetRef(BaseModel):
    producer: str
    name: str
    media_type: str


class NodeIR(BaseModel):
    node_id: str
    capability: str
    inputs: list[AssetRef] = Field(default_factory=list)
    output_type: str
    max_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]


class WorkflowIR(BaseModel):
    version: Literal["agent-ir/v1"]
    workflow_id: str
    nodes: list[NodeIR]
    total_budget: float = Field(gt=0)

    @model_validator(mode="after")
    def validate_graph(self) -> "WorkflowIR":
        ids = [node.node_id for node in self.nodes]
        if len(ids) != len(set(ids)):
            raise ValueError("duplicate_node_id")

        known = set(ids)
        for node in self.nodes:
            for ref in node.inputs:
                if ref.producer not in known:
                    raise ValueError(
                        f"unknown_producer:{ref.producer}"
                    )
        return self

这段定义没有检查环路、数据类型和拓扑顺序,这些工作应由独立静态分析器完成。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.planvision.storyboardvideo.compose。最后一遍根据区域、租户权限、上下文长度、价格版本和近期质量,把能力绑定到候选池,而不是单一模型。

运行时仍可在候选池内做健康切换,但不能越过编译器设定的质量与合规边界。

计划完成后计算哈希并签名。运行时收到的每个节点都携带 plan_hash,任何动态追加节点都必须生成新版本并重新通过检查。这能阻止执行器或模型在未审计的情况下扩大权限。

签名不是为了证明模型输出正确,而是为了证明实际执行的程序与已审核计划一致。

5. 策略即代码的静态分析

企业规则不应散落在 Prompt 中,例如"客户数据不得离开指定区域""生产发布必须人工确认""单次视频任务不得超过预算上限"。

这些规则应编写为针对 IR 的可测试策略,在编译阶段返回允许、拒绝或要求补充条件。诊断信息必须指向具体节点和字段,而不是只返回"策略不通过"。

策略之间还可能冲突:低延迟策略要求跨区域后端,数据策略却禁止出区;低成本策略选择轻量模型,质量策略又要求高置信度。

编译器应按照硬约束优先、软目标优化的方式求解,并在无可行解时明确列出冲突集合。沉默地忽略某条规则会让系统表面成功,却破坏治理边界。

6. 一个简单的拓扑编译器

python 复制代码
from collections import defaultdict, deque
from dataclasses import dataclass


@dataclass(frozen=True)
class Step:
    name: str
    deps: tuple[str, ...]
    p95_ms: int


def compile_levels(
    steps: list[Step]
) -> list[list[str]]:
    by_name = {
        step.name: step
        for step in steps
    }
    indegree = {
        step.name: 0
        for step in steps
    }
    children: dict[str, list[str]] = defaultdict(list)

    for step in steps:
        for dep in step.deps:
            if dep not in by_name:
                raise ValueError(
                    f"unknown_dependency:{dep}"
                )
            indegree[step.name] += 1
            children[dep].append(step.name)

    ready = deque(
        name
        for name, degree in indegree.items()
        if degree == 0
    )
    levels: list[list[str]] = []
    visited = 0

    while ready:
        current = list(ready)
        ready.clear()
        levels.append(current)

        for name in current:
            visited += 1
            for child in children[name]:
                indegree[child] -= 1
                if indegree[child] == 0:
                    ready.append(child)

    if visited != len(steps):
        raise ValueError("cycle_detected")

    return levels

levels 中同一层的节点具备并行执行条件,但运行时还要结合并发额度、内存、租户配额和上游频控做二次调度。

编译器证明"可以并行",运行时决定"此刻是否应该并行"。

四、把 Token 当作计算资源:成本模型、准入控制与路由

1. 从请求计费转向计划计费

企业通常按单次请求统计模型成本,但 Agent 的真实计费单元应是一张执行计划。一次用户请求可能触发规划、检索、脚本生成、图像理解和多轮修复。若只看最终模型,最昂贵的循环会隐藏在中间节点。

设节点 i 的输入、输出 Token 上限分别为 I_iO_i,对应单价为 p_iq_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_ADMITTEDROUTE_BOUNDREQUEST_SENTFIRST_TOKENASSET_STOREDVALIDATION_FAILEDRETRY_SCHEDULEDNODE_COMMITTED。当前状态由事件折叠得到,原始事件不可修改。

这种 Event Sourcing 设计允许重建任意时间点的状态,也能计算排队、网络、推理、验证分别花了多少时间。

日志中不直接保存敏感 Prompt,而是保存脱敏摘要、内容哈希和加密资产引用。观测系统通过 trace_id 将网关请求、上游 request ID、工具执行和资产写入串成一条链路。

3. 用 Saga 管理跨系统副作用

Agent 很难使用传统数据库的全局事务:模型服务、对象存储、消息系统和视频队列不会参加同一个两阶段提交。更现实的方式是 Saga。

每个可逆节点声明补偿动作,例如创建临时素材的补偿是删除素材,写入草稿的补偿是恢复旧版本。

补偿不是简单倒序重试。系统先判断哪些步骤已经提交、哪些补偿仍然合法,再根据依赖关系执行。不可逆节点没有真正的补偿,只能通过审批屏障、幂等键和执行回执降低风险。

若外部调用超时但结果未知,节点进入 UNCERTAIN,先查询回执,不能立即重复提交。

4. Memory 应成为显式状态输入

短期对话、长期知识和工作流中间状态必须分离。长期 Memory 写入前经过来源校验、去重、脱敏、租户隔离和过期设置;读取后以带来源的资产进入 IR,而不是偷偷拼接到系统提示词。

这样编译器可以计算它占用的上下文预算,也能在回放时恢复同一版本。

上下文压缩同样属于编译优化。系统按照约束优先级、相关度、可信度和时间衰减选择内容,压缩后执行"约束存活检查"。

若禁止事项、输出 Schema 或用户已确认选择丢失,编译器必须重新调整,而不是让模型在缺少关键条件的上下文中继续执行。

Memory 的写回也要通过验证。模型在一次任务中推断出的临时结论不能自动升级为长期事实;只有带可靠来源、通过一致性检查并满足保留策略的信息才能进入长期存储。

写回事件保存来源任务、证据资产和置信度,后续事实被更正时可以按 lineage 定位并使相关记忆失效。否则错误会随着每次检索不断自我强化。

5. 取消必须沿任务图传播

用户取消根任务时,运行时先阻止新节点入队,再取消没有副作用的在途请求,最后处理已经提交的外部任务。

取消结果分为 CANCELLEDTOO_LATECOMPENSATING,不能用一个布尔值覆盖。对于不支持取消的视频后端,系统记录孤儿任务,完成后按策略归档或删除资产,避免资源长期泄漏。

六、后端驱动层:用统一网关隔离协议碎片

1. 网关是驱动层,不是业务大脑

不同模型接口在消息字段、工具调用、流式事件、错误结构和计费明细上存在差异。文本接口通常同步或 SSE 返回,图像接口可能直接返回资产,视频接口往往只返回任务句柄。

如果编译器理解这些私有格式,后端替换就会影响计划语义。

统一网关只做五件事:校验内部请求、解析租户凭证、把能力别名映射为驱动、转换协议、归一化响应与错误。业务层的预算审批、工作流依赖和内容验收不应放进网关。

这样的边界类似操作系统调用与设备驱动:上层声明意图,驱动处理具体设备差异。

在协议联调环境中,可以将 OpenAI Compatible Protocol 的统一接口测试上游配置为 base_url="https://178.nz/bo",凭证从密钥服务注入;这一地址仅承担请求转换、SSE 解析、错误映射与 Token 计量的沙盒验证,不作为代码中的固定生产依赖。

2. 统一请求信封

请求信封至少包含 request_idtenant_idplan_hashnode_idcapabilitydeadline_atidempotency_keybudgetcontentresponse_schema

网关收到请求后先校验截止时间与幂等键,再解析候选驱动。所有上游凭证都由网关持有,Agent 运行时只使用短时内部身份。

错误被归一为 INVALID_REQUESTAUTH_FAILEDRATE_LIMITEDUPSTREAM_TIMEOUTSAFETY_REJECTEDCONTEXT_OVERFLOWUNKNOWN_RESULT

只有 RATE_LIMITED 与部分超时适合自动重试;参数错误、安全拒绝和上下文越界需要回到编译器或业务策略处理。

3. FastAPI 驱动入口

python 复制代码
import asyncio
import os
from datetime import datetime, timezone
from typing import Any

import httpx
from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel, Field


app = FastAPI()


class Envelope(BaseModel):
    plan_hash: str
    node_id: str
    capability: str
    deadline_at: datetime
    idempotency_key: str
    budget: 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"
    ],
}


def remaining_seconds(
    deadline: datetime
) -> float:
    now = datetime.now(timezone.utc)
    return max(
        (deadline - now).total_seconds(),
        0.0
    )


async def invoke_driver(
    url: str,
    body: dict[str, Any],
    timeout: float
):
    limits = httpx.Limits(
        max_connections=200,
        max_keepalive_connections=40
    )
    async with httpx.AsyncClient(
        limits=limits,
        http2=True
    ) as client:
        response = await client.post(
            url,
            json=body,
            timeout=timeout
        )
        if response.status_code == 429:
            raise RuntimeError("RATE_LIMITED")
        response.raise_for_status()
        return response.json()


@app.post("/internal/v1/invoke")
async def invoke(
    envelope: Envelope,
    x_tenant_id: str = Header()
):
    url = DRIVERS.get(envelope.capability)
    if url is None:
        raise HTTPException(
            400,
            "UNSUPPORTED_CAPABILITY"
        )

    remaining = remaining_seconds(
        envelope.deadline_at
    )
    if remaining <= 0:
        raise HTTPException(
            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 = await asyncio.wait_for(
            invoke_driver(
                url,
                payload,
                remaining
            ),
            timeout=remaining,
        )
    except asyncio.TimeoutError as exc:
        raise HTTPException(
            504,
            "UPSTREAM_TIMEOUT"
        ) from exc
    except RuntimeError as exc:
        raise HTTPException(
            503,
            str(exc)
        ) from exc

    return {
        "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.planreasoning.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 从试验脚本推进到可测试、可审计、可演进的软件基础设施。

相关推荐
IT小盘12 小时前
12-Prompt不等于一句话-System-User-Context三层结构
java·windows·prompt
安逸sgr1 天前
Prompt 优化和微调有什么区别?什么场景优先用 Prompt?
人工智能·ai·大模型·prompt·agent·智能体
井川廊咏1 天前
重建 AI 认知第 3 篇:Prompt Engineering——怎么让 AI 听懂你的话
人工智能·chatgpt·prompt
风雨中的小七2 天前
解密Prompt系列71. 从DSpark聊聊大模型 Decoding 提速的技术演化
prompt
qq_454245032 天前
认知自举:LLM自我指令泛化的三层逻辑
人工智能·架构·prompt
神奇霸王龙2 天前
金融AI对决:Qwen3.7-Max屠榜降本60%
人工智能·ai·金融·prompt·aigc·ai金融
神奇霸王龙2 天前
DeepSeek医疗RAG降本60%实战
人工智能·gpt·ai·prompt·音视频·医疗·ai医疗
AI分享猿2 天前
文生图Prompt怎么写:把模糊创意拆成七个可控要素
prompt
qq_454245033 天前
Cline智能体系统提示词
人工智能·prompt