把大模型接进项目,第一步通常很简单:一个 API 调用,发消息,拿回答。但真正在企业里上生产的场景,需要的远不止一次对话。这篇文章讲一个更实际的问题:当 AI 需要完成「多步骤、有分支、依赖外部系统」的复杂任务时,工作流该怎么设计。
一、单 LLM 调用的边界在哪里
先明确一件事:不是所有场景都需要 Agent 编排。单次 LLM 调用能很好解决的场景:
· 文本润色、翻译、摘要——输入输出都是文本,一次搞定
· 知识问答——有了 RAG 之后,一次检索 + 一次生成足够
· 内容生成——写文章、写代码,单次调用已经够用
但当任务变成「读取用户提交的报修单 → 判断类型 → 查询当前空闲人员 → 创建工单 → 发送通知」,单次调用就不够了。这个任务的特点是:
· 有多个步骤,每步依赖上一步的输出
· 需要调用外部系统(工单系统、人员系统)
· 有条件分支(不同类型走不同处理路径)
· 可能需要等待(人工审批)
二、 Agent 编排的核心概念
2.1 节点(Node)
工作流的基本单元。每个节点完成一件事:一次 LLM 调用、一次 API 请求、一段代码逻辑、一个条件判断或一个人工介入点。节点之间通过输入输出约定连接。
2.2 有向无环图 ( DAG )
大多数工作流用 DAG 来描述执行顺序。DAG 的核心约束是「无环」——不能有循环依赖。如果业务逻辑需要循环(比如「生成 → 评估 → 不合格则重新生成」),通常用有限次数的展开或单独的循环节点来处理。
2.3 上下文传递
节点间传递的不只是「上一步的输出文本」,而是一个结构化的 context 对象。设计好 context schema 是整个编排稳定运行的关键。
| // context 示例{'session_id': 'xxx','user_input': '电梯故障,3栋1单元','extracted': {'type': '电梯故障','location': '3栋1单元','urgency': 'high'},'available_staff': ['张工', '李工'],'ticket_id': null, // 后续节点填充'notification_sent': false} |
|---|
三、条件分支与并行执行
3.1 条件分支
根据上一步的输出,走不同路径。实现有两种思路:
· 规则路由: 用 if/else 或 switch 判断,适合逻辑明确、枚举完整的场景。好处是可预测,坏处是维护成本随规则增加而上升
· LLM 路由: 把决策交给 LLM,让模型判断「这个输入应该走哪个分支」。灵活,但不可预测,不推荐用于关键业务路径
3.2 并行 执行
多个独立的节点可以并行运行,减少等待时间。典型场景:同时查询多个数据源,再把结果合并。要注意合并节点的设计——需要处理某个子节点失败的情况。
| # 伪代码:并行查询三个系统async def parallel_query(ticket_info):results = await asyncio.gather(query_staff_system(ticket_info),query_history_db(ticket_info),query_sla_rules(ticket_info),return_exceptions=True # 某个失败不影响其他)return merge_results(results) |
|---|
四、人工介入( Human in the Loop )
不是所有决策都该交给 AI。设计良好的工作流,应该在合适的节点插入人工审批:
· 金额超过阈值的报销,AI 预填信息,财务审批
· 法律文件的最终确认
· AI 置信度低于某个值时,自动转人工
人工介入节点的技术实现通常是:工作流暂停,把当前 context 写入数据库,向相关人员发通知,等待人工操作后恢复执行。这里的难点是超时处理和并发控制。
五、错误处理与重试策略
生产环境里 LLM 调用会超时,外部 API 会挂,这些都要有预案:
· 幂等设计: 工作流中的每个节点应该是幂等的——重复执行同一步骤不应产生副作用
· 断点 续跑: 把 context 持久化,某个节点失败后可以从断点重新执行,不用从头开始
· 降级策略: 某个模型不可用时,自动切换到备用模型;某个数据源查询失败时,用缓存数据继续
| @retry(max_attempts=3, backoff=exponential)def call_llm_node(node_input, model='deepseek-chat'):try:return llm_client.complete(node_input)except RateLimitError:return llm_client.complete(node_input, model='qwen-plus') # 降级except TimeoutError:return cached_response(node_input) # 用缓存 |
|---|
六、可观测性:你得知道每步发生了什么
Agent 编排最难调试的问题:某个任务结果不对,但不知道是哪一步出的问题。解决方案是给每个节点打埋点:
· 记录节点的输入、输出、执行时间、模型调用的 token 消耗
· 给每次执行生成唯一的 trace_id,方便追踪完整链路
· 关键决策节点记录「为什么走了这条路」
| 工程实践补充如果你不想从零搭一套编排引擎,可以考虑现有的开源或商业方案。开源方案里 LangGraph 的图结构设计比较完善;如果需要企业级权限控制、资产管理和可视化编排界面,ZGI(zgi.cn)提供了一套完整的工作流引擎,底层是 Golang 自研,支持私有化部署。 |
|---|