AI Agent 工作流引擎工程实践:从 Prompt 清单到状态机驱动的生产级流程控制

你的 Agent 能干活,但它能可靠地干活吗?当工作流从 3 步扩展到 15 步,从 demo 走进生产,"Prompt 里写了规则"就不再够用。本文拆解 Agent 工作流引擎的核心设计模式、生产陷阱与选型决策框架。

一个真实的生产事故

凌晨 2 点,客服 Agent 在处理退款争议时崩溃了。

不是因为模型出了问题,而是因为它在第 7 步------调用支付系统 API 确认退款金额------收到了一个网络超时。没有断点续跑机制,整个工作流从头开始。但"从头开始"意味着它重新发起了一次退款请求。客户收到了两笔退款。

这不是假设场景。2026 年 Q3,我们团队在处理的一个 Agent 系统就踩了这个坑。根因很简单:工作流的状态只活在内存里,进程挂了,状态就没了。

这个问题在 Agent 工程社区里有一个正式的名字:Durable Execution 缺失。而围绕它的更大问题是:Agent 的工作流到底应该怎么管理?

Prompt 清单的天花板

大多数团队的 Agent 工作流管理方式是这样的:

text 复制代码
# Agent 工作流规则(写在 System Prompt 里)

1. 先分析用户需求,生成分析计划
2. 按计划执行每一步
3. 每步执行后验证结果
4. 如果验证失败,重试最多 3 次
5. 所有步骤完成后生成总结报告
6. 遇到敏感操作(删除、支付、发邮件),等待人工确认

这种方式能跑,但它有三个致命问题:

第一,Agent 会"忘记"规则。 当 context window 被工具调用的输出塞满,System Prompt 里的规则会被注意力机制"稀释"。我们实测过一个 7 步工作流:在步骤 5 之后,Agent 跳过验证步骤直接给出结论的概率从 2% 上升到 34%。

第二,没有强制的状态转移。 Prompt 说"先分析再执行",但 Agent 完全可以跳过分析直接动手------它只是在文本层面"同意"了规则,运行时没有任何东西阻止它偏离流程。

第三,失败路径不可控。 "重试 3 次"写在 Prompt 里,但重试的计数器也在 Prompt 管理的 context 里。Agent 可能记错重试次数,或者把不同任务的重试次数混在一起。

这不是 Prompt 的错。Prompt 擅长描述意图,不擅长强制执行流程。

状态机:把工作流从"建议"变成"法律"

2026 年 Agent 工程社区的一个明确趋势是:用有限状态机(FSM)来管理 Agent 的工作流。

核心思路很直接:把工作流的每个阶段定义为一个状态,状态之间的转移需要满足特定条件(gate),每个状态产出必须附带可验证的证据(evidence)。

一个典型的 Agent 工作流状态机:

ruby 复制代码
┌─────────┐    task contract    ┌──────────┐    scoped plan    ┌─────────────┐
│  INTAKE  │ ─────────────────→ │   PLAN   │ ────────────────→ │ IMPLEMENT   │
└─────────┘                     └──────────┘                    └──────┬──────┘
    ↑                              ↑ revise                           │
    │ reject                       │                                   ▼
    │                              │                            ┌─────────────┐
    │                              └─────────────────────────── │  VERIFY     │
    │                                                           └──────┬──────┘
    │                                                                  │
    │    ┌──────────┐    retry count < 3    ┌─────────────┐           │ fail
    └─── │   END    │ ←──────────────────── │   REPAIR    │ ←─────────┘
         └──────────┘                       └──────┬──────┘
              ↑                                    │ retry count ≥ 3
              │         evidence attached           │
              └────────────────────────────────────┘ escalate

每个状态的定义包括三个要素:

  1. 允许的动作(allowed actions):在这个状态下,Agent 被允许做什么
  2. 转移条件(transition gate):离开这个状态需要提交什么证据
  3. 失败路径(failure routing):如果当前步骤失败,工作流走向哪里

用 TypeScript 来表达:

typescript 复制代码
interface WorkflowState {
  name: string;
  allowedActions: string[];
  transitionGate: (evidence: Evidence) => GateResult;
  failureRoute: string; // 失败时转移到的状态
  maxRetries?: number;
}

interface Evidence {
  type: 'plan' | 'code_diff' | 'test_result' | 'approval' | 'report';
  payload: unknown;
  timestamp: number;
  checksum: string;
}

// 状态机定义
const refundWorkflow: WorkflowState[] = [
  {
    name: 'intake',
    allowedActions: ['analyze_request', 'request_clarification', 'reject'],
    transitionGate: (evidence) => {
      if (evidence.type !== 'plan') return { pass: false, reason: '需要任务合同' };
      const plan = evidence.payload as TaskContract;
      if (!plan.userId || !plan.orderId || !plan.reason) {
        return { pass: false, reason: '任务合同缺少必填字段' };
      }
      return { pass: true };
    },
    failureRoute: 'reject',
  },
  {
    name: 'verify_refund',
    allowedActions: ['check_transaction', 'check_policy', 'calculate_amount'],
    transitionGate: (evidence) => {
      if (evidence.type !== 'test_result') return { pass: false, reason: '需要验证结果' };
      const result = evidence.payload as VerificationResult;
      if (!result.transactionFound) return { pass: false, reason: '交易记录未找到' };
      if (!result.policyMatch) return { pass: false, reason: '不符合退款政策' };
      return { pass: true };
    },
    failureRoute: 'repair',
    maxRetries: 2,
  },
  {
    name: 'execute_refund',
    allowedActions: ['call_payment_api'],
    transitionGate: (evidence) => {
      if (evidence.type !== 'test_result') return { pass: false, reason: '需要执行确认' };
      const result = evidence.payload as RefundResult;
      if (!result.refundId) return { pass: false, reason: '退款未成功创建' };
      return { pass: true };
    },
    failureRoute: 'repair',
    maxRetries: 1, // 支付操作只允许重试 1 次
  },
];

这段代码的关键不在于具体的字段设计,而在于它把"Agent 应该怎么做"从自然语言描述变成了可执行的约束。Agent 不能在 intake 状态下调用支付 API,因为 call_payment_api 不在它的 allowedActions 里。它不能跳过 verify_refund 直接进入 execute_refund,因为状态机没有定义这条转移路径。

LangGraph vs Temporal vs 自建 FSM:选型决策框架

2026 年主流的 Agent 工作流方案大致分三类:

1. LangGraph:图即工作流

LangGraph 把工作流表达为有向图,每个节点是一个处理步骤,边定义了条件转移。它跟主流 Agent 框架集成最紧密。

python 复制代码
from langgraph.graph import StateGraph, START, END

def analyze(state: AgentState) -> dict:
    plan = llm.invoke("分析用户需求:" + state["input"])
    return {"plan": plan, "messages": [plan]}

def execute(state: AgentState) -> dict:
    result = execute_plan(state["plan"])
    return {"result": result}

def should_repair(state: AgentState) -> str:
    if state.get("verify_passed"):
        return "report"
    if state.get("retry_count", 0) >= 3:
        return "escalate"
    return "repair"

graph = StateGraph(AgentState)
graph.add_node("analyze", analyze)
graph.add_node("execute", execute)
graph.add_node("verify", verify)
graph.add_node("repair", repair)
graph.add_node("report", report)

graph.add_edge(START, "analyze")
graph.add_edge("analyze", "execute")
graph.add_edge("execute", "verify")
graph.add_conditional_edges("verify", should_repair)
graph.add_edge("repair", "verify")
graph.add_edge("report", END)

app = graph.compile(checkpointer=MemorySaver())

优势: 可视化直观,checkpoint 机制内置。 劣势: 状态持久化依赖外部 checkpointer,生产级 durability 需要额外集成。图的条件边越多,调试越困难。

2. Temporal / Restate:代码即工作流

Temporal 和 Restate 这类 code-first 工作流引擎,让你在普通代码里用 await 写工作流,引擎自动处理状态持久化和断点续跑。

python 复制代码
from temporalio import workflow, activity

@activity.defn
async def call_payment_api(refund_request: RefundRequest) -> RefundResult:
    return await payment_client.refund(refund_request)

@workflow.defn
class RefundWorkflow:
    @workflow.run
    async def run(self, request: RefundRequest) -> str:
        # 步骤 1:分析请求(持久化)
        analysis = await workflow.execute_activity(
            analyze_request, request,
            start_to_close_timeout=timedelta(seconds=30)
        )

        # 步骤 2:验证(持久化)
        verification = await workflow.execute_activity(
            verify_refund, analysis,
            start_to_close_timeout=timedelta(seconds=30)
        )

        if not verification.approved:
            return "rejected"

        # 步骤 3:执行退款(持久化,幂等)
        result = await workflow.execute_activity(
            call_payment_api, request,
            start_to_close_timeout=timedelta(seconds=60),
            retry_policy=RetryPolicy(maximum_attempts=2)
        )

        return result.refund_id

优势: 代码即工作流,没有 DSL 学习曲线;内置状态持久化、断点续跑、超时管理;适合长时任务(小时级甚至天级)。 劣势: 需要部署 Server(运维成本);与 Agent 框架的集成需要额外适配层。

3. 自建 FSM + Checkpoint:轻量但够用

对于中小规模的 Agent 工作流,一个自建的状态机加上 checkpoint 存储就够用了。

typescript 复制代码
class WorkflowEngine {
  private state: WorkflowState;
  private context: Record<string, unknown>;
  private checkpointStore: CheckpointStore;
  private retryCounters: Map<string, number>;

  async start(workflowId: string, input: unknown) {
    const saved = await this.checkpointStore.load(workflowId);
    if (saved) {
      this.state = saved.state;
      this.context = saved.context;
      this.retryCounters = saved.retryCounters;
    } else {
      this.state = this.states[0];
      this.context = { input };
      this.retryCounters = new Map();
    }

    while (this.state.name !== 'end') {
      try {
        const evidence = await this.executeState(this.state, this.context);
        const gateResult = this.state.transitionGate(evidence);

        if (gateResult.pass) {
          this.context = { ...this.context, ...evidence.payload };
          this.state = this.getNextState(this.state, 'success');
        } else {
          const retries = this.retryCounters.get(this.state.name) || 0;
          const maxRetries = this.state.maxRetries || 3;

          if (retries < maxRetries) {
            this.retryCounters.set(this.state.name, retries + 1);
          } else {
            this.state = this.getNextState(this.state, 'failure');
          }
        }

        await this.checkpointStore.save(workflowId, {
          state: this.state,
          context: this.context,
          retryCounters: this.retryCounters,
          timestamp: Date.now(),
        });

      } catch (error) {
        console.error(`Workflow ${workflowId} failed:`, error);
        this.state = this.getNextState(this.state, 'failure');
      }
    }

    return this.context;
  }
}

优势: 完全可控,没有外部依赖,容易跟现有系统集成。 劣势: 需要自己处理并发、分布式锁、超时等底层问题。

选型对照表

维度 LangGraph Temporal/Restate 自建 FSM
学习曲线 低(图即流程) 中(SDK 概念) 低(代码即流程)
状态持久化 需外接 checkpointer 内置 需自建
断点续跑 通过 checkpoint 原生支持 需自建
可视化 内置 Web UI 无(需自建)
Agent 框架集成 主流生态原生 需适配层 完全自定义
适合场景 中等复杂度 长时任务,高可靠性 简单流程,快速迭代
运维成本 低 中-高 低

生产环境的 5 个工程陷阱

陷阱 1:Checkpoint 粒度不对

最常见的错误是只在状态转移时 checkpoint。如果某个步骤执行了 5 分钟,在第 4 分钟崩溃了,那 4 分钟的工作就白做了。

正确做法是区分 粗粒度 checkpoint (状态转移时保存)和 细粒度 checkpoint(关键操作前后保存):

typescript 复制代码
async function executeWithFineCheckpoint(state: WorkflowState, context: Context) {
  await checkpoint('state_enter', { state: state.name, context });

  for (const action of state.allowedActions) {
    const result = await executeAction(action, context);
    await checkpoint('action_complete', {
      state: state.name,
      action,
      result,
      context: mergeContext(context, result),
    });
  }

  return context;
}

一个真实数据:我们把 checkpoint 粒度从"每状态"改为"每操作"后,工作流的平均恢复时间从 3.2 分钟降到 12 秒。

陷阱 2:状态爆炸

当工作流的条件分支超过 8 个时,状态数量会以指数级增长。一个原本 6 步的线性流程,加上各种异常路径后,可能膨胀到 40+ 个状态。

解法是 分层状态机:把复杂工作流拆成子工作流,每个子工作流有自己的状态机,父工作流只管理子工作流之间的转移。

typescript 复制代码
const parentWorkflow: WorkflowState[] = [
  { name: 'triage', failureRoute: 'escalate' },
  { name: 'run_verification_subflow', failureRoute: 'manual_review' },
  { name: 'run_refund_subflow', failureRoute: 'manual_review' },
  { name: 'notify_customer', failureRoute: 'log_and_continue' },
  { name: 'end' },
];

const verificationSubflow: WorkflowState[] = [
  { name: 'check_transaction', failureRoute: 'repair' },
  { name: 'check_policy', failureRoute: 'repair' },
  { name: 'calculate_amount', failureRoute: 'repair' },
  { name: 'end' },
];

陷阱 3:证据验证变成了形式主义

状态机要求每个状态转移时提交证据,但如果证据验证太松,Agent 就会学会"应付检查"------生成一份看起来像回事但实际上没用的证据。

我们见过一个案例:Agent 在验证步骤提交了一个 test_result 类型的证据,payload 是 {"passed": true}。状态机的 gate 检查了 evidence.type === 'test_result',就放行了。但实际的测试根本没跑。

解法:证据必须包含可验证的执行痕迹。

typescript 复制代码
const strictEvidenceGate: TransitionGate = (evidence) => {
  if (evidence.type !== 'test_result') return { pass: false, reason: '需要测试结果' };

  const result = evidence.payload as TestResult;

  if (!result.commandOutput || result.commandOutput.length < 50) {
    return { pass: false, reason: '命令输出太短,疑似伪造' };
  }

  if (!result.timestamp || result.timestamp > Date.now()) {
    return { pass: false, reason: '时间戳无效' };
  }

  if (computeChecksum(result.commandOutput) !== evidence.checksum) {
    return { pass: false, reason: '证据完整性校验失败' };
  }

  return { pass: true };
};

陷阱 4:重试风暴

当一个步骤失败并触发重试时,如果上游有多个实例同时重试,可能形成"重试风暴"------所有实例在同一时刻对同一个下游服务发起请求。

这在退款场景中尤其危险。解法是 指数退避 + jitter + 全局去重:

typescript 复制代码
async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  maxRetries: number,
  baseDelayMs: number,
  dedupKey: string
): Promise<T> {
  const lock = await acquireDistributedLock(dedupKey, baseDelayMs * 2);
  if (!lock.acquired) {
    return await waitForResult(dedupKey);
  }

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const result = await fn();
      await releaseLock(lock);
      return result;
    } catch (error) {
      const delay = baseDelayMs * Math.pow(2, attempt) + Math.random() * 1000;
      await sleep(delay);

      const existingResult = await checkIdempotentResult(dedupKey);
      if (existingResult) {
        await releaseLock(lock);
        return existingResult;
      }
    }
  }

  await releaseLock(lock);
  throw new Error(`重试 ${maxRetries} 次后仍然失败`);
}

陷阱 5:工作流版本管理的缺失

这是最容易被忽视的陷阱。工作流定义修改了(比如新增了一个验证步骤),但还有 50 个正在进行中的工作流实例运行的是旧版本。新版本部署后,这些实例从 checkpoint 恢复时,发现状态机结构变了,直接崩溃。

解法是 工作流版本化 + 兼容性迁移:

typescript 复制代码
interface WorkflowVersion {
  version: string;
  states: WorkflowState[];
  migrations: Record<string, (context: unknown) => unknown>;
}

const workflowV2: WorkflowVersion = {
  version: '2.0',
  states: [/* 新版本的状态定义 */],
  migrations: {
    '1.0': (context: unknown) => {
      const ctx = context as V1Context;
      return {
        ...ctx,
        risk_score: ctx.risk_score || calculateRiskScore(ctx),
        compliance_check: ctx.compliance_check || 'pending',
      };
    },
  },
};

async function resumeWorkflow(workflowId: string) {
  const saved = await checkpointStore.load(workflowId);
  const savedVersion = saved.version;
  const currentVersion = workflowV2.version;

  if (savedVersion !== currentVersion) {
    const migration = workflowV2.migrations[savedVersion];
    if (!migration) {
      throw new Error(`无法从版本 ${savedVersion} 迁移到 ${currentVersion}`);
    }
    saved.context = migration(saved.context);
    saved.version = currentVersion;
  }

  return engine.resume(saved);
}

决策框架:什么时候用状态机,什么时候不用

不是所有 Agent 任务都适合状态机管理。过度工程化同样有害。

适合状态机的场景:

  • 流程步骤确定,可以提前枚举
  • 步骤之间有明确的先后依赖
  • 失败的代价高(支付、数据删除、外部通知)
  • 需要审计日志和合规记录
  • 多步骤的 CRUD 操作链

不适合状态机的场景:

  • 探索性研究("帮我调研 XX 领域的最新进展")
  • 开放式头脑风暴
  • 调试和问题诊断(失败模式未知)
  • 单步骤的简单任务
  • 需要 Agent 自主决定流程走向的创新任务

一个实用的判断标准:如果你能画出这个任务的工作流图,它就适合状态机管理;如果你画不出来,就别硬画。

可观测性:工作流引擎不是黑盒

工作流引擎的每一步都应该产生可观测性数据。三个关键指标:

1. 状态转移延迟(State Transition Latency)

每个状态的平均停留时间。如果某个状态的 p99 延迟突然飙升,说明这个步骤的下游可能出了问题。

typescript 复制代码
metrics.histogram('workflow.state_duration_ms', duration, {
  workflow_id: workflowId,
  state: state.name,
  version: workflowVersion,
});

2. 转移成功率(Transition Success Rate)

每个状态的转移成功/失败比例。如果某个状态的成功率低于阈值,触发告警。

3. Checkpoint 大小增长率(Checkpoint Growth Rate)

如果 checkpoint 数据增长过快,说明 context 在膨胀,可能需要做 context 裁剪。我们见过一个案例,checkpoint 从 2KB 增长到 12MB,导致恢复时间从毫秒级变成秒级。

写在最后

Agent 工作流引擎不是一个新概念------传统工作流引擎(Airflow、Temporal、Camunda)已经存在了很多年。但 Agent 时代的工作流引擎需要解决一个传统引擎不需要面对的问题:执行者(LLM)是非确定性的。

传统工作流引擎假设每个步骤的执行结果是确定的(给定相同的输入)。Agent 不是。同一个 Prompt、同一个上下文,Agent 可能产出不同的结果。这意味着工作流引擎的 gate 验证、证据检查、重试策略都需要为这种不确定性做额外设计。

2026 年的趋势很明确:Agent 工作流正在从"Prompt 描述"走向"代码强制"。LangGraph 提供了图结构,Temporal 提供了持久化执行,新兴方案提供了可验证执行。选择哪个取决于你的场景复杂度、可靠性要求和团队技术栈。

但无论选择哪个方向,核心原则不变:流程应该在代码里,而不是在 Prompt 的想象里。


参考来源:

  • Aharness (GitHub): 工作流的有限状态机实现
  • LangGraph 文档:Graph-based agent orchestration
  • Temporal 文档:Durable execution for long-running workflows
  • Developer's Digest: "Agent Workflows as Code: Why State Machines Beat Prompt Checklists" (2026-06)
  • LangChain Blog: "How to Build a Custom Agent Harness" (2026)
相关推荐
ServBay16 小时前
DeepSeek 小更新,许愿式编程成真了?
aigc·ai编程·deepseek
殷紫川18 小时前
Context7 :给 AI 编码代理接上实时文档的 “活水”
aigc·ai编程·mcp
Behavior18 小时前
某餐饮品牌开放 MCP 接口被玩成消费级 Agent:WorkBuddy 搭出会算营养、比价格、能下单的减脂私教
aigc·agent·mcp
niaonao19 小时前
刷短剧上头后,我用5090和ComfyUI部署MiniMax H3
aigc
程序员天天困19 小时前
DeepSeek-V4.1-Flash-1017 上手实测:3D 直出,效果到底如何
aigc·ai编程·deepseek
小虎AI生活21 小时前
OpenAI 披露 19 起 AI 失控记录,用 AI 提效的第一步为什么不是学提示词
aigc·ai编程
Dawson Zhu1 天前
【AI Infra小白入门⑦】Transformer架构工程拆解:从Attention到MLA的优化密码
人工智能·语言模型·架构·aigc·agi
Csvn1 天前
灰度发布:让变更只影响 1% 用户(R02)
人工智能·aigc·agent
黑科技iOS上架1 天前
软件需求中什么时候需要agent能力?
aigc