从 Prompt Engineering 到 Agent Workflow:中级开发者构建可落地 AI 自动化流程的实践指南
很多开发者第一次接触大模型时,都是从“写一个好 Prompt”开始的:
给模型一段上下文,补几条约束,再加几个示例,效果立刻就比“裸问”强不少。
但只要你尝试把它放进真实业务,就会很快遇到几个问题:
- 单次 Prompt 效果不错,但流程一长就不稳定
- 模型会“看起来很懂”,但关键步骤并不可靠
- 一旦接入外部系统,问题从“回答得好不好”升级为“能不能执行、能不能回滚、能不能审计”
- 业务方想要的不是一个聪明聊天窗口,而是一个可落地、可监控、可维护的 AI 自动化流程
这时,你会发现:
Prompt Engineering 很重要,但它只是起点;真正让 AI 产生业务价值的,是 Agent Workflow。
这篇文章我会从中级开发者的视角,把这条路径拆开讲清楚:
从提示词设计,到任务拆分、工具调用、状态管理、异常处理,最后形成一个能跑起来的自动化工作流。
背景与问题
为什么“只会写 Prompt”还不够
Prompt Engineering 解决的是:如何让模型更好地完成一次推理。
而 Agent Workflow 解决的是:如何让模型在一个多步骤流程中,稳定地完成任务并与系统协作。
可以把两者理解成:
- Prompt Engineering:偏“单次交互优化”
- Agent Workflow:偏“端到端任务编排”
比如一个“自动处理客服工单”的需求:
- 读取用户工单内容
- 判断意图与优先级
- 查询知识库
- 如果缺信息,自动生成追问
- 如果是退款类问题,调用订单系统核验
- 生成最终回复
- 记录处理日志和置信度
这里已经不是一个 Prompt 能优雅解决的事情了。
你需要的是:
- 明确的步骤划分
- 可控的工具调用
- 中间状态存储
- 错误兜底策略
- 人工介入节点
- 可观测性与审计
中级开发者最容易卡住的地方
我见过不少团队在这一步翻车,原因通常不是模型不够强,而是工程设计太“聊天化”:
- 把整个任务塞进一个超长 Prompt
- 期待模型自己“规划、执行、纠错、总结”
- 没有结构化输出约束
- 工具调用没有超时、重试、幂等设计
- 日志只记最终答案,不记中间推理和动作
- 没有区分“模型擅长判断”与“系统必须确定执行”的边界
所以,进入 Agent Workflow 之前,得先建立一个原则:
让模型负责智能判断,让系统负责确定性执行。
核心原理
这一部分是整篇文章的骨架。你只要把这几个原则抓住,后面的工程实现就顺了。
1. 从“生成回答”转向“完成任务”
Prompt 的核心目标通常是“输出一段尽可能好的文本”。
Agent Workflow 的核心目标则是“在给定约束下完成业务任务”。
这意味着你设计系统时,需要先问:
- 任务的输入是什么?
- 任务的完成标准是什么?
- 哪些步骤必须确定性执行?
- 哪些步骤适合交给模型判断?
- 失败后如何补救?
2. Agent 不等于“一个会自己行动的大模型”
更准确的理解是:
Agent = 大模型 + 工具集 + 工作流状态 + 执行策略
它不是神奇黑箱,而是一个被编排的系统组件。
一个可落地的 Agent Workflow,通常至少包含:
- Planner / Router:决定任务路径
- Tool Executor:调用外部系统
- Memory / State Store:保存上下文与中间结果
- Validator:校验输出是否合法
- Fallback / Human-in-the-loop:异常时切换兜底逻辑
3. Prompt 的角色发生了变化
在单轮问答中,Prompt 是主角。
在工作流中,Prompt 更像是某个节点的“行为配置”。
也就是说,你不该追求“一个万能 Prompt”,而应该为不同节点设计不同 Prompt:
- 分类节点 Prompt
- 信息抽取节点 Prompt
- 工具选择节点 Prompt
- 回复生成节点 Prompt
- 审核节点 Prompt
4. 工作流优先于自由自治
很多人一上来就想做“全自动自主 Agent”,但现实里更推荐的是:
- 先做固定流程
- 再做条件分支
- 再做有限自治
- 最后才考虑开放式规划
因为开放式自治虽然酷,但也是最难测、最难控、最难审计的。
一张图看懂:从 Prompt 到 Workflow 的升级路径
flowchart LR
A[单次 Prompt 调用] --> B[结构化输出]
B --> C[多步骤链式处理]
C --> D[接入工具调用]
D --> E[引入状态管理]
E --> F[异常重试与回滚]
F --> G[可观测/审计/人工介入]
G --> H[可落地 Agent Workflow]
这条路径非常符合大多数团队的演进规律。
不要跳级,真的。很多坑我都是在“试图一步到位”时踩出来的。
一个实用设计范式:把任务拆成 4 层
为了避免系统越写越乱,我通常建议用下面这个分层思路:
第 1 层:输入规范化
把原始用户输入转成结构化上下文:
- 用户原文
- 渠道
- 时间
- 历史记录
- 业务标识
- 附件摘要
第 2 层:语义决策
交给模型做擅长的判断,例如:
- 意图分类
- 紧急程度识别
- 是否需要补充信息
- 该走哪个流程分支
第 3 层:确定性执行
交给程序和外部系统做:
- 查数据库
- 调订单服务
- 写 CRM
- 发邮件 / 发消息
- 记录审计日志
第 4 层:结果生成与校验
再由模型负责组织语言,但输出前必须经过:
- 字段校验
- 业务规则校验
- 敏感词/越权内容校验
工作流时序示意
sequenceDiagram
participant U as 用户
participant API as 业务服务
participant LLM as 大模型
participant KB as 知识库
participant OMS as 订单系统
participant LOG as 日志系统
U->>API: 提交工单
API->>LLM: 意图分类/信息抽取
LLM-->>API: 结构化结果
API->>KB: 检索相关知识
KB-->>API: 文档片段
API->>OMS: 查询订单状态
OMS-->>API: 订单详情
API->>LLM: 基于上下文生成回复
LLM-->>API: 回复草稿
API->>LOG: 记录中间状态与结果
API-->>U: 返回处理结果
注意这个图里有个关键点:
模型不直接操作业务系统,而是由 API 层控制调用。
这能显著提升安全性、可测性和回放能力。
核心原理落地:一个最小可用 Agent Workflow
下面我们用 Python 写一个可运行的最小示例:
场景:自动处理简化版售后工单
目标:
- 识别工单类型
- 如果是退款,查询订单
- 如果信息不足,要求补充
- 生成最终回复
- 输出完整处理结果
为了便于你直接运行,我这里用“模拟 LLM”方式演示工作流骨架。
如果你要接真实模型,把 mock_llm_* 函数换成实际 API 调用即可。
实战代码(可运行)
目录中的核心代码
import json
from dataclasses import dataclass, asdict
from typing import Optional, Dict, Any
@dataclass
class Ticket:
user_id: str
ticket_id: str
message: str
@dataclass
class WorkflowState:
intent: Optional[str] = None
urgency: Optional[str] = None
needs_more_info: bool = False
order_id: Optional[str] = None
order_status: Optional[str] = None
final_reply: Optional[str] = None
error: Optional[str] = None
# ---------- 模拟 LLM ----------
def mock_llm_classify(message: str) -> Dict[str, Any]:
text = message.lower()
result = {
"intent": "other",
"urgency": "low",
"needs_more_info": False,
"order_id": None
}
if "退款" in message or "refund" in text:
result["intent"] = "refund"
result["urgency"] = "medium"
if "尽快" in message or "马上" in message:
result["urgency"] = "high"
# 简单提取订单号:假设格式为 ORD123
import re
match = re.search(r"ORD\d+", message)
if match:
result["order_id"] = match.group(0)
if result["intent"] == "refund" and not result["order_id"]:
result["needs_more_info"] = True
return result
def mock_llm_reply(context: Dict[str, Any]) -> str:
if context["needs_more_info"]:
return "您好,为了帮您处理退款申请,请补充订单号(例如 ORD123)。"
if context["intent"] == "refund":
status = context.get("order_status", "未知")
return f"您好,已收到您的退款请求。当前订单状态为:{status}。我们将按售后流程继续处理。"
return "您好,已收到您的问题,我们会尽快为您处理。"
# ---------- 模拟工具 ----------
def query_order_system(order_id: str) -> Dict[str, str]:
mock_db = {
"ORD100": {"status": "已支付"},
"ORD200": {"status": "已发货"},
"ORD300": {"status": "已完成"},
}
return mock_db.get(order_id, {"status": "订单不存在"})
# ---------- 工作流 ----------
def process_ticket(ticket: Ticket) -> WorkflowState:
state = WorkflowState()
try:
# Step 1: LLM 分类与信息抽取
classify_result = mock_llm_classify(ticket.message)
state.intent = classify_result["intent"]
state.urgency = classify_result["urgency"]
state.needs_more_info = classify_result["needs_more_info"]
state.order_id = classify_result["order_id"]
# Step 2: 条件分支 - 查询订单
if state.intent == "refund" and state.order_id and not state.needs_more_info:
order_result = query_order_system(state.order_id)
state.order_status = order_result["status"]
# Step 3: 生成回复
state.final_reply = mock_llm_reply({
"intent": state.intent,
"urgency": state.urgency,
"needs_more_info": state.needs_more_info,
"order_id": state.order_id,
"order_status": state.order_status
})
except Exception as e:
state.error = str(e)
state.final_reply = "系统正在处理中,请稍后再试或联系人工客服。"
return state
if __name__ == "__main__":
tickets = [
Ticket(user_id="U1", ticket_id="T1", message="我要申请退款,订单号是 ORD200"),
Ticket(user_id="U2", ticket_id="T2", message="我要退款,请尽快处理"),
Ticket(user_id="U3", ticket_id="T3", message="你们这个商品怎么开发票?"),
]
for t in tickets:
result = process_ticket(t)
print(f"\n=== Ticket {t.ticket_id} ===")
print(json.dumps(asdict(result), ensure_ascii=False, indent=2))
运行结果你应该关注什么
不是只看“有没有回复”,而是看这些字段是否完整:
intenturgencyneeds_more_infoorder_idorder_statusfinal_replyerror
这就是从“文本输出”走向“结构化状态机”的第一步。
为什么这段代码比“一个大 Prompt”更靠谱
因为它把问题拆开了:
- 分类与抽取:交给模型
- 订单查询:交给工具
- 回复生成:再交给模型
- 异常处理:交给程序
这就是工程上的边界感。
如果你把这些全塞进一个 Prompt,模型可能会:
- 编造订单状态
- 忘记缺少订单号时应该追问
- 输出格式不稳定
- 在长上下文中漏掉关键条件
进一步升级:加入状态机思维
当工作流复杂起来时,我建议你显式维护状态,而不是隐式拼 Prompt。
stateDiagram-v2
[*] --> Received
Received --> Classified
Classified --> NeedMoreInfo: 信息不足
Classified --> QueryOrder: 退款且有订单号
Classified --> DraftReply: 普通咨询
QueryOrder --> DraftReply
NeedMoreInfo --> WaitingUser
DraftReply --> Validated
Validated --> Done
Validated --> HumanReview: 校验失败
HumanReview --> Done
一旦你开始用状态图思考,就会自然考虑:
- 哪些状态可重试?
- 哪些状态必须人工介入?
- 哪些状态需要持久化?
- 哪些状态变更要写审计日志?
这也是把 Demo 变成生产系统的关键一步。
Prompt Engineering 在 Workflow 里该怎么写
不是不要 Prompt,而是要节点化设计。
例 1:分类节点 Prompt
你是售后工单分类助手。
请根据用户输入识别以下字段,并只输出 JSON:
- intent: refund / invoice / logistics / other
- urgency: low / medium / high
- needs_more_info: true / false
- order_id: 字符串或 null
规则:
1. 如果用户表达退款诉求,则 intent=refund
2. 如果退款但缺少订单号,则 needs_more_info=true
3. 不要输出解释,不要输出 markdown
例 2:回复生成节点 Prompt
你是客服回复助手。
请根据提供的结构化上下文,生成简洁、礼貌、可直接发送给用户的中文回复。
要求:
1. 不得编造订单信息
2. 如果信息不足,优先要求补充
3. 语气自然,不要过度承诺
中级开发者该有的一个习惯
每个 Prompt 只做一件事。
如果一个 Prompt 同时承担:
- 分类
- 检索判断
- 工具选择
- 回复生成
- 合规审查
那它大概率会变成你未来最难维护的一坨配置。
常见坑与排查
这一节非常重要。很多系统不是设计错了,而是排查手段太弱。
坑 1:输出不稳定,JSON 经常解析失败
现象
模型有时输出 JSON,有时前面加解释,有时字段拼错。
原因
- Prompt 约束不够强
- 没有做 schema 校验
- 把“自然语言回复”与“机器可解析结果”混在一起
排查与修复
- 强制模型只输出 JSON
- 对输出做
json.loads+ schema 校验 - 如果失败,走一次自动修复或重试
- 最好把“结构化决策”和“自然语言生成”拆成两个调用
示例校验代码:
def validate_classify_result(data: dict) -> bool:
required_keys = ["intent", "urgency", "needs_more_info", "order_id"]
for key in required_keys:
if key not in data:
return False
if data["intent"] not in ["refund", "invoice", "logistics", "other"]:
return False
if data["urgency"] not in ["low", "medium", "high"]:
return False
if not isinstance(data["needs_more_info"], bool):
return False
return True
坑 2:模型“看起来会”,但工具调用很乱
现象
模型选错工具、漏传参数、重复调用。
原因
- 工具定义不清晰
- 没有限制调用条件
- 没有幂等设计
- 没有调用日志
建议
- 工具接口一定要小而清晰
- 参数必须有类型和默认值
- 每次调用都记录
tool_name / input / output / duration / status - 对高风险动作加确认门槛
坑 3:上下文越来越长,效果越来越差
现象
流程多跑几轮后,模型开始遗漏条件、答非所问、成本暴涨。
原因
- 把所有历史对话都原样塞进去
- 没有摘要机制
- 没有按任务阶段裁剪上下文
建议
保留三类上下文即可:
- 当前任务必需字段
- 最近相关交互
- 关键事实摘要
而不是“能塞多少塞多少”。
坑 4:异常处理只做了 try/except
现象
出了问题时只能返回“系统繁忙”。
更好的做法
区分异常类型:
- 模型调用超时
- 工具调用失败
- 输出校验失败
- 业务规则冲突
- 权限不足
不同异常走不同兜底策略,而不是一把抓。
坑 5:把 Prompt 调优当成唯一优化手段
这是我自己早期最容易犯的错。
效果不好时,总想着“再改几句 Prompt”。
但很多时候真正的问题是:
- 流程拆分不合理
- 工具返回信息不足
- 状态字段设计不完整
- 校验规则缺失
Prompt 优化有用,但不能替代流程设计。
安全/性能最佳实践
AI 工作流一进业务系统,安全和性能就不是附属品,而是主线。
1. 最小权限原则
不要让模型直接拥有数据库写权限、支付权限、退款权限。
正确做法:
- 模型提出建议
- 服务端校验
- 工具层按白名单执行
- 高风险操作必须人工确认
2. 防 Prompt Injection
尤其是接入网页、邮件、知识库、用户上传文档时,要警惕外部内容污染提示词。
建议:
- 将用户内容与系统指令物理隔离
- 对外部文本做清洗和截断
- 不允许外部文本覆盖系统角色定义
- 工具调用必须经过服务端授权判定
3. 输出校验先于执行
模型输出任何用于执行的内容前,都必须经过程序校验:
- 参数类型
- 枚举值
- ID 格式
- 权限范围
- 业务规则
4. 超时、重试、熔断要补齐
典型建议:
- LLM 调用设置超时
- 工具调用做指数退避重试
- 外部依赖失败时熔断
- 给每个任务分配 trace id
5. 成本控制要前置
Agent Workflow 比单次问答更容易“悄悄烧钱”。
建议监控这些指标:
- 单任务 token 消耗
- 平均工具调用次数
- 每个节点延迟
- 重试率
- 人工接管率
- 任务成功率
6. 可观测性比“聪明”更重要
线上系统最怕的不是偶发错误,而是“出错了但你不知道为什么”。
建议每次任务都记录:
- 输入摘要
- 节点执行顺序
- 每个节点的模型输入/输出摘要
- 工具调用结果
- 状态变更
- 最终决策来源
一个更贴近生产的工程建议
如果你准备把这类工作流真正上线,我推荐按下面的最小架构来搭:
flowchart TD
A[客户端/业务入口] --> B[Workflow Orchestrator]
B --> C[Prompt Node: 分类]
B --> D[Tool Node: 查询订单]
B --> E[Prompt Node: 回复生成]
B --> F[Validator Node]
B --> G[Human Review Node]
B --> H[State Store]
B --> I[Logs & Metrics]
这个架构的好处
- 节点职责清晰
- 每一步都能单测
- 方便局部替换模型或工具
- 利于灰度发布
- 出了问题能快速定位
对于中级开发者来说,这类“简单但清楚”的架构,比追求花哨框架更重要。
一个逐步落地的实施顺序
如果你现在手上就有业务需求,不妨按这个顺序推进:
第 1 步:先做结构化输出
不要先追求自治。
先让模型稳定输出 JSON。
第 2 步:拆出独立节点
把分类、抽取、回复生成拆开。
第 3 步:接入一个确定性工具
比如查订单、查库存、查知识库。
先把“模型判断 + 系统执行”的闭环跑通。
第 4 步:加状态持久化
至少要能记录:
- 当前任务在哪一步
- 中间结果是什么
- 失败在哪里
第 5 步:补校验、重试、日志
这是从 Demo 到可用系统的分水岭。
第 6 步:最后再考虑更复杂的 Agent 能力
例如:
- 动态规划
- 多 Agent 协作
- 长时记忆
- 自动反思与重试
这些都可以做,但不要抢跑。
总结
从 Prompt Engineering 到 Agent Workflow,本质上不是“提示词升级”,而是一次工程思维升级:
- 从一次回答,转向完整任务
- 从文本生成,转向状态驱动
- 从模型主导,转向模型与系统协作
- 从“能演示”,转向“能落地”
如果你是中级开发者,我最建议你记住这 5 条:
- Prompt 不是全部,工作流设计更关键
- 让模型负责判断,让系统负责执行
- 每个节点只做一件事
- 结构化输出、状态管理、校验机制必须有
- 先做固定流程,再逐步提高自治程度
边界条件也很明确:
- 如果业务需要强审计、强合规、强确定性,优先 workflow,不要放任模型自由发挥
- 如果任务高度开放、探索性强,可以适度增加 Agent 自治,但必须保留监控和回滚能力
- 如果团队还没有稳定的日志、测试、配置管理基础,不建议一上来做复杂多 Agent 系统
一句话收尾:
能上线的 AI,不是最会说的那个,而是最能被控制、被验证、被维护的那个。
如果你先把一个小而稳的 Agent Workflow 跑起来,再逐步扩展能力,这条路会比你反复打磨“万能 Prompt”走得更快。