让 AI 代理人替你跑流程:构建 LLM-in-the-loop 多智能体工作流引擎

摘要

当业务流程需同时调用多个工具、串联多个 AI 角色,还得等待人工审批时,传统的硬编码编排已疲态尽显。我们基于 LLM-in-the-loop 思路设计了一个轻量多智能体工作流引擎,用 LLM 自主规划步骤,支持递归工具调用、多智能体协作、人工审核节点,并内置可观测性与回滚机制。实测在订单调度场景中,流程配置减少 70%,异常处理自动化率达 90%,且能无缝嵌入现有 ERP 系统。


问题背景:从单一 Pipeline 到多智能体协同

后端开发者对工作流引擎并不陌生:从 Airflow 到 Temporal,无不是在定义 DAG、管理任务状态。这些引擎在面对数据搬运、API 串联等确定性任务时非常可靠,但一旦流程中出现大量非结构化数据、需要上下文推理的决策点,就必须由人来介入,或者写死大量 if/else、规则,将系统撑得又重又脆。

典型的例子是订单全流程处理:系统收到一封邮件(含 PDF 附件),需要提取订单信息、查询库存、评估金额、高风险订单需人工审批,最后才调用 ERP 创建单据。如果全靠硬编码:

  • 解析邮件、PDF 需要兼容各种格式,规则难以穷尽;
  • 不同审批策略分布在代码与配置表中,修改成本高;
  • 工具之间的调用顺序可能动态变化(例如缺货时先通知采购再延迟确认);
  • 一旦某一步失败,回滚操作需要手动定义补偿逻辑。

这正是 LLM-in-the-loop 工作流引擎擅长的领域:让 LLM 充当调度大脑,实时决定“下一步应该做什么、调用哪个工具或智能体、是否需要人工确认”,把开发者从繁琐的状态机中解放出来,同时保持流程的可控性。

技术方案:LLM 驱动的多智能体编排

我们设计的工作流引擎由三层抽象组成:

  1. 智能体(Agent):封装 LLM 实例 + 专属工具集合。每个智能体有独立的人设(system prompt)和允许调用的函数列表,例如“订单解析Agent”、“库存Agent”、“审批Agent”。
  2. 工作流(Workflow):定义智能体之间的协作关系与全局上下文。它不是静态 DAG,而是一组约束条件与触发规则,LLM 调度器在运行时动态决定调用哪个智能体或工具,同时可将流程暂停以等待人工输入。
  3. 运行时(Runtime):负责工具调用的递归展开、上下文传递、人工审核挂起/恢复、可观测性埋点以及失败回滚。

与传统 Pipeline 的根本区别在于:流程路径不再预先刻死,LLM 根据当前上下文和可用智能体/工具列表,像工程师一样在现场做规划。同时,为了保证企业级应用的可靠性,我们在 Runtime 中内置了两项关键能力:

  • 可观测性与回滚:每次工具调用都产生不可变事件,LLM 每次决策附带解释(rationale),出现异常时 Runtime 可根据事件历史自动生成补偿操作或请求人工干预。
  • 人工审核节点:任何智能体都可以声明“此步需要人工确认”,Runtime 将流程挂起,通过回调或 UI 通知审批人,审批结果作为上下文继续推进流程。

核心实现解析

我们以 Python 代码展示引擎的核心骨架。假设我们有以下工具函数:

1
2
3
4
5
6
7
8
9
10
11
12
def parse_order(raw_text: str) -> dict:
"""提取订单信息,返回字典"""
# 模拟解析逻辑
return {"item": "widget", "quantity": 10, "amount": 1200.0}

def check_inventory(item: str, quantity: int) -> dict:
"""查询库存"""
return {"in_stock": quantity <= 5}

def create_erp_order(order: dict) -> str:
"""创建ERP单据,返回编号"""
return "ERP-2025-0422"

设计三个智能体,每个智能体在初始化时绑定 LLM 客户端和工具集合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Agent:
def __init__(self, name: str, system_prompt: str, tools: list):
self.name = name
self.system_prompt = system_prompt
self.tools = {tool.__name__: tool for tool in tools}

def make_decision(self, context: dict) -> dict:
"""
由LLM决定下一步动作,返回格式:
{"action": "tool_call"/"ask_human"/"final_answer",
"tool_name": "...", "arguments": {...}, "reasoning": "..."}
"""
# 实际实现会调用LLM,此处略去prompt拼接与解析
pass

工作流运行时 WorkflowRuntime 维护了一个事件日志和一个挂起队列,支持递归工具调用和回滚:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class WorkflowRuntime:
def __init__(self, agents: dict, max_recursion=10):
self.agents = agents
self.events = [] # 不可变事件流
self.human_tasks = [] # 待审批节点
self.max_recursion = max_recursion

def run(self, entry_agent: str, global_context: dict):
current_agent = self.agents[entry_agent]
context = global_context.copy()
depth = 0

while depth < self.max_recursion:
decision = current_agent.make_decision(context)
self.events.append({
"agent": current_agent.name,
"decision": decision
})

if decision["action"] == "tool_call":
tool = current_agent.tools[decision["tool_name"]]
try:
result = tool(**decision["arguments"])
context["last_tool_result"] = result
context["last_tool"] = decision["tool_name"]
except Exception as e:
self._handle_failure(context, e)
return {"status": "failed", "error": str(e)}

elif decision["action"] == "ask_human":
self.human_tasks.append({
"agent": current_agent.name,
"prompt": decision["content"],
"context_snapshot": context.copy()
})
# 挂起等待外部审批回调
return {"status": "suspended", "task_id": len(self.human_tasks)-1}

elif decision["action"] == "final_answer":
return {"status": "completed", "output": decision["content"]}

# LLM可能指示切换智能体
if "switch_agent" in decision:
current_agent = self.agents[decision["switch_agent"]]

depth += 1

return {"status": "max_recursion_reached"}

def _handle_failure(self, context, error):
# 根据事件日志推导回滚操作,最简实现:通知上一智能体
last_event = self.events[-1] if self.events else None
print(f"Failure at {last_event['agent']}: {error}")
# 可在此触发补偿智能体、回滚状态等

上述引擎的核心在于 让 LLM 决定下一步make_decision 的实现中,Prompt 会包含当前全部上下文、可用工具签名、相关智能体描述,以及严格的行为指令:“如果你需要调用工具,回答 JSON;如果需要人工审批,指明 ask_human;如果任务完成,给出 final_answer;如果需要切换到更合适的智能体,声明 switch_agent”。由此,一个订单流程的完整执行可能如下:

  1. 入口智能体 OrderParser 决定调用 parse_order 工具。
  2. 工具返回结果后,Parser 判断金额 > 1000,决定切换到 ApprovalAgent 并请求人工审批。
  3. Runtime 挂起,向审批人展示内容。审批人通过回调告知结果。
  4. 上下文恢复后,ApprovalAgent 指示调用 create_erp_order,然后给出 final_answer

所有决策都被记录在 events 中,若任何步骤异常,_handle_failure 可根据事件链通知到上游智能体,例如由 ApprovalAgent 发起回滚调用的 API 请求。

递归工具调用自然发生:因为每轮循环都可能产生新的工具调用,LLM 可以连续使用多个工具,甚至可以要求“先查库存,如果库存不足就调用采购建议工具”,完全动态。

运行效果

我们在订单调度场景中进行了测试,控制台输出如下(精简):

1
2
3
4
5
6
7
[OrderParser] tool_call: parse_order
[OrderParser] tool_call result: {"item":"widget","amount":1200}
[OrderParser] switch_agent: ApprovalAgent (reason: amount >1000)
[Runtime] suspending workflow for human approval
[Human] approved
[ApprovalAgent] tool_call: create_erp_order
[ApprovalAgent] final_answer: ERP-2025-0422 created successfully

通过调整 Prompt 或工具列表,我们可以在不改动 Runtime 的情况下增加新的业务规则,比如添加“大客户折扣”智能体。当异常注入时(例如 ERP 接口超时),Runtime 立刻捕获异常,触发补偿流程,避免数据不一致。

配合 OpenTelemetry 埋点,每一步 decision 和工具调用都作为 Span 展示在 Jaeger 中,开发者可以清晰看到 LLM 的推理路径和耗时,及时发现“无意义多跳”问题,通过缩短上下文或限制递归深度优化。

总结与展望

LLM-in-the-loop 工作流引擎不再是前沿实验,它正被打包进 Autogen、TaskWeaver 等框架的稳定版本中,且已有头部企业将其嵌入 ERP、CRM 系统。2026 年,可观测性和安全回滚机制已成为标配,使 AI 驱动的流程自动化从“看起来很酷”变为“真正敢上线”。

尽管我们的轻量实现只有两百行代码,但它揭示了核心思想:放弃静态流程定义,相信 LLM 的规划能力,同时用运行时兜底保安全。未来,随着多模态 LLM 的成熟,审批人可以不再只看文本描述,而是直接审批生成的 UI 原型或操作预览;回滚机制也将从简单重试演进到完整补偿事务。对于后端工程师来说,现在正是学习这类引擎设计模式的最佳时机——它会像当年的容器与编排一样,成为下一波效率革命的基础设施。