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

0 阅读9分钟

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

一个真实的生产事故

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

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

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

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

Prompt 清单的天花板

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

# 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 工作流状态机:

┌─────────┐    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 来表达:

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 框架集成最紧密。

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 写工作流,引擎自动处理状态持久化和断点续跑。

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 存储就够用了。

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;
  }
}

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

选型对照表

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

生产环境的 5 个工程陷阱

陷阱 1:Checkpoint 粒度不对

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

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

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+ 个状态。

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

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',就放行了。但实际的测试根本没跑。

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

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 + 全局去重:

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 恢复时,发现状态机结构变了,直接崩溃。

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

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 延迟突然飙升,说明这个步骤的下游可能出了问题。

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)