01|Agent 到底是什么?何时该用,何时不该用
Agent 工程实战系列 · 序章 ·
一、问题场景:老板说“给客服做个 AI 助手”
你是 SupportPilot(客服工单助手)项目的开发。一周之内,业务方丢来三个需求:
- “用户问运费和退货政策,自动回答。”
- “用户申请退款:核对订单,校验是否在 7 天内,金额小于 200 元自动退,其余转人工。”
- “用户说‘我的包裹好像丢了’,助手要自己查订单、查物流、判断是否异常,再决定补发、退款还是转人工。”
三个需求都被称为“AI 助手”,但它们是同一种东西吗?都应该做成 Agent 吗?
答案是否定的。很多团队的第一个坑,就是把这三件事混为一谈:要么给简单问题上了复杂架构,要么让一个“聊天机器人”去干需要自主决策的活。本篇给你一把尺子,用来判断一个需求落在哪一档,并带你用 50 行代码亲手跑通最小的 Agent。
二、核心概念
2.1 一个判断标准:谁决定下一步
区分 Chatbot、Workflow、Agent,不看它“多聪明”,也不看它“调用了几个工具”,只问一个问题:
下一步做什么,是开发者在代码里写死的,还是模型在运行时自己决定的?
| 层级 | 谁决定下一步 | 典型形态 | SupportPilot 中的例子 |
|---|---|---|---|
| 0 单次调用 | 没有“下一步” | 提示词 → 回答 | 翻译用户留言、判断情绪 |
| 1 Workflow(工作流) | 代码(路径由开发者预先写死) | 提示链、路由、并行 | 需求 2:退款流程 |
| 2 Agent | 模型(运行时自己选工具、定步数、决定何时停) | 模型 + 工具 + 循环 | 需求 3:包裹丢失 |
| 3 多 Agent | 多个模型相互委派 | 编排者-执行者 | 第 10、17 篇 |
需求 1 属于第 0 到第 1 档:检索一次、生成一次,路径是固定的。有检索,不等于是 Agent。
这条光谱不是“越往下越高级”,而是“把多少决定权交给模型”。决定权越多,灵活性越强,可预测性越弱。
打个比方:Workflow 像柜员照着 SOP 办业务,每一步都有规定;Agent 像一个拿到目标、权限和工具的实习生,怎么做他自己想办法,你得操心的是他会不会乱来。
2.2 Agent = 模型 + 工具 + 循环
目标 → [模型决策] → 调用工具 → 观察结果 → 回到[模型决策] → …… → 模型认为完成 / 触发上限 → 结束- 模型:负责判断“现在该做什么”。
- 工具:模型的手脚,用来读写外部世界(查订单、查知识库、发邮件)。
- 循环:让“观察到的结果”能影响“下一步决策”。没有循环,工具调用只是一次性的 function calling。
- 终止条件:最容易被忽略的第四个要素。没有它,Agent 可能永远不停,或者在不该停的时候停。
2.3 四个常见误解
| 误解 | 实际情况 |
|---|---|
| 有工具调用就是 Agent | 工具调用只是一次动作。路径写死在代码里,就是 Workflow |
| 多步骤就是 Agent | 多步但路径固定,是提示链。Agent 的标志是路径由模型动态决定 |
| 用了 RAG 就是 Agent | 检索一次再回答是流水线。让模型自己决定查什么、查几次、查不到换什么,才靠近 Agent(第 09 篇) |
| 多 Agent 比单 Agent 高级 | 往往更贵、更难调试,多数场景单 Agent 加好工具就够了(第 10 篇) |
三、设计取舍:何时用,何时不用
3.1 Agent 的账单
把决策权交给模型是有代价的——它得反复思考、反复调用,每一步都在烧 token,是要付费的。把同一件事分别做成 Workflow 和 Agent,差别如下:
| 维度 | Workflow | Agent |
|---|---|---|
| 延迟 | 固定,可预估 | 步数不定,通常是单次调用的数倍 |
| 成本 | 可预算 | 随步数增长,有长尾(偶发走很多步) |
| 可预测性 | 路径固定 | 同一输入可能走不同路径 |
| 可测试 | 对每个节点单独测试 | 需要评估整条轨迹(第 18 篇) |
| 可调试 | 看哪个节点出错 | 需要完整 Trace 才能还原(第 19 篇) |
| 安全面 | 权限随节点固定 | 模型可能触达所有被授权的工具(第 22–24 篇) |
| 灵活性 | 只覆盖预设情形 | 能处理没见过的组合 |
因此有一条贯穿全系列的原则:默认从最简单的方案开始,只有实测发现简单方案不够用,才增加AI自己决定多少的程度。这也是业界在总结 Agent 工程实践时反复强调的思路。
3.2 产物 ①:Agent 适用性决策表
拿到一个需求,依次回答下面 7 个问题:
| # | 问题 | 回答后的结论 |
|---|---|---|
| 1 | 步骤能否在开发时穷举? | 能 → 用 Workflow 或单次调用 |
| 2 | 下一步是否取决于上一步的结果,且分支多到写不完? | 是 → 倾向 Agent |
| 3 | 需要的工具和数据,是否已有可调用的接口? | 没有 → 先建接口,别急着做 Agent |
| 4 | 成败能否被自动判定(有明确验收标准)? | 不能 → 先补验收标准,否则无法评估和迭代 |
| 5 | 出错后能否撤销,或有人兜底? | 不能 → 限制AI自己决定多少的程度:只读 + 人工确认写操作 |
| 6 | 能否接受数倍延迟和不固定的成本? | 不能 → 退回 Workflow |
| 7 | 任务价值是否覆盖成本? | 不能 → 不做 |
把它压缩成一张决策流程:
Q1 步骤能穷举? ── 能 ──→ 用 Workflow(或单次调用) │ 不能 Q2 成败能自动判定? ── 不能 ──→ 先补验收标准,暂不做 Agent │ 能 Q3 工具和数据接口已就绪? ── 否 ──→ 先建工具,再回来 │ 是 Q4 出错能撤销或有人兜底? ── 否 ──→ 只读 Agent,写操作必须人工确认 │ 能 Q5 延迟、成本可接受,价值覆盖成本? ── 否 ──→ 退回 Workflow │ 是 适合 Agent:从“单 Agent + 少量工具”开始3.3 用决策表判定 SupportPilot 的三个需求
| 需求 | 判定 | 理由 |
|---|---|---|
| 1 政策问答 | 单次调用 + RAG | 一次检索、一次生成,路径固定,不需要循环 |
| 2 退款流程 | Workflow | 规则明确、步骤可穷举,合规要求结果确定。模型只负责节点内的“理解用户意图、抽取订单号” |
| 3 包裹丢失 | Agent(只读起步) | 查什么、查几次取决于中间结果,路径写不完。但补发、退款属于写操作,先加人工确认 |
一个产品里三种形态并存是正常且健康的。实践中常见的组合是:Workflow 做骨架,Agent 做关节,即在固定流程的某个节点里嵌一个小 Agent 处理开放性问题。
四、动手实现:50 行最小 Agent
准备环境(示例使用 DeepSeek Python SDK,换成其他厂商的函数调用接口,结构完全一样):
pipinstallopenaiexportDEEPSEEK_API_KEY=你的密钥# Windows PowerShell: setx DEEPSEEK_API_KEY "你的密钥"新建agent_min.py:
importjson,osfromopenaiimportOpenAI client=OpenAI(api_key=os.environ['DEEPSEEK_API_KEY'],# 读取环境变量base_url='https://api.deepseek.com',# DeepSeek 的 OpenAI 兼容端点)MODEL='deepseek-v4-pro'# DeepSeek 4 Pro;也可换成 deepseek-v4-flash 省钱MAX_STEPS=8# 预算:防止死循环ORDERS={'A1001':{'status':'已发货','eta':'2026-10-14'},'A1002':{'status':'待付款','eta':None}}defget_order(order_id:str)->dict:returnORDERS.get(order_id)or{'error':f'订单{order_id}不存在'}TOOLS=[{'type':'function','function':{'name':'get_order','description':'按订单号查询订单状态和预计送达时间。只读,不会修改订单。','parameters':{'type':'object','properties':{'order_id':{'type':'string','description':'订单号,形如 A1001'}},'required':['order_id'],},},}]HANDLERS={'get_order':get_order}SYSTEM='你是客服助手。需要订单信息时必须调用工具,不要凭空猜测;信息不足时直接向用户提问。'defrun(user_msg:str)->str:messages=[{'role':'system','content':SYSTEM},{'role':'user','content':user_msg}]forstepinrange(MAX_STEPS):# 循环:决策 → 行动 → 观察resp=client.chat.completions.create(model=MODEL,max_tokens=1024,tools=TOOLS,messages=messages)msg=resp.choices[0].message messages.append(msg)# 决策进入上下文ifnotmsg.tool_calls:# 模型没有再要工具,可以收尾returnmsg.contentor''forcallinmsg.tool_calls:# 行动 + 观察args=json.loads(call.function.argumentsor'{}')print(f'[step{step}] 调用{call.function.name}({args})')try:out=HANDLERS[call.function.name](**args)exceptExceptionase:# 错误也回传,让模型自己调整out={'error':str(e)}messages.append({'role':'tool','tool_call_id':call.id,'content':json.dumps(out,ensure_ascii=False)})return'(达到步数上限,已停止,转人工处理)'if__name__=='__main__':print(run('帮我查下订单 A1001 什么时候到?'))读懂这 50 行
| 代码位置 | 对应概念 | 为什么重要 |
|---|---|---|
for step in range(MAX_STEPS) | 循环 + 终止条件 | 没有上限,工具持续失败时会无限循环、成本失控 |
not msg.tool_calls不是 tool_use 就收尾 | 模型决定何时停 | 这是“模型决定下一步”的体现:Workflow 里这一步由代码决定 |
try/except里回传is_error | 错误也是观察 | 模型看到错误后可以改参数重试或向用户澄清,而不是让程序崩溃 |
messages.append(...)两处 | 上下文即状态 | 工具结果必须放回消息历史,模型下一轮才“看得见”(第 05 篇详讲) |
跑几个小实验
把上述代码中的def run(user_msg: str) -> str: 函数 改成:
defrun(messages:list,user_msg:str)->str:"""处理一轮提问;messages 由外部传入并持续累积,实现多轮记忆。"""messages.append({'role':'user','content':user_msg})forstepinrange(MAX_STEPS):# 循环:决策 → 行动 → 观察resp=client.chat.completions.create(model=MODEL,max_tokens=1024,tools=TOOLS,messages=messages)msg=resp.choices[0].message messages.append(msg)# 决策进入上下文ifnotmsg.tool_calls:# 模型没有再要工具,可以收尾returnmsg.contentor''forcallinmsg.tool_calls:# 行动 + 观察args=json.loads(call.function.argumentsor'{}')print(f'[step{step}] 调用{call.function.name}({args})')try:out=HANDLERS[call.function.name](**args)exceptExceptionase:# 错误也回传,让模型自己调整out={'error':str(e)}messages.append({'role':'tool','tool_call_id':call.id,'content':json.dumps(out,ensure_ascii=False)})return'(达到步数上限,已停止,转人工处理)'if__name__=='__main__':messages=[{'role':'system','content':SYSTEM}]# 整轮会话共享print('客服 Agent 已启动,输入 exit 或 quit 退出。')whileTrue:try:user_msg=input('\n你: ').strip()except(EOFError,KeyboardInterrupt):# Ctrl+C / Ctrl+D 优雅退出print('\n已退出。')breakifnotuser_msg:continueifuser_msg.lower()in('exit','quit'):print('已退出。')breakprint('Agent:',run(messages,user_msg))实际输出因模型而异,重点是观察行为模式:
- 正常路径:问“订单 A1001 什么时候到”,观察一次工具调用后给出回答。
- 多次调用:问“A1001 和 A1002 哪个先到”,观察模型是否一次发起两个调用(代码已兼容并行的多个调用)。
- 工具报错:问“订单 A9999 到哪了”,观察工具返回错误后,模型如何向用户解释。
- 触发预算:把
MAX_STEPS改成 1,再问第 1 题,观察兜底话术。 - 去掉约束:删掉
SYSTEM里“不要凭空猜测”一句,不提供订单号直接问“我的货到哪了”,观察它会追问还是编造。
五、踩坑清单:典型失败模式
| 失败模式 | 现象 | 根因 | 预防(对应篇目) |
|---|---|---|---|
| 用 Agent 做固定流程 | 退款审批偶尔跳过“7 天内”校验 | 把必须执行的规则交给模型自觉 | 规则放进代码,模型只做理解(03、04) |
| 无限循环、步数爆炸 | 工具持续报错,模型反复用同样参数重试 | 没有步数、成本、时间上限;错误信息没有指引 | 三重预算 + 错误信息写明下一步建议(08、13) |
| 工具选错、参数瞎填 | 调了不该调的工具,或编出订单号 | 工具描述含糊、功能重叠 | 工具是“给模型看的界面”(06) |
| 凭空编造 | 没查到数据,却给出确定的答复 | 没约束“无证据则追问或转人工” | 证据绑定与核验(25) |
| 无法评估就上线 | “感觉还行”,改一次提示词就出新问题 | 没有成功标准和测试集 | 先写验收标准(03、18) |
| 万物皆 Agent | 为一次文本分类搭了循环和工具链 | 把“用上新技术”当成了目标 | 先用决策表自查(本篇) |
一个反例,感受“用 Agent 做固定流程”的代价:某团队把退款流程做成 Agent,让模型自己决定“先查订单还是直接退款”。演示时一切正常,上线后偶尔出现模型跳过了时效校验就直接退款的情况。原因不难理解:校验是否执行,取决于模型每次的判断,而判断有概率波动。把校验写进 Workflow 的固定节点之后,问题消失,调用次数也更少、更便宜。
六、自测
本篇练习
- 练习:挑一个你手头的真实需求,按下面的模板写 3 行判定。
需求:…… 自治层级判定(0 / 1 / 2 / 3):…… 关键理由(引用决策表第几问):…… 最大风险:……自测 5 题
- 用户上传发票图片,系统用模型提取金额后写入财务系统。这是 Agent 吗?
- 某团队让模型“自己决定是否先做风控校验再放款”。这个设计有什么问题?
- 一个流程固定 5 步,每步调用一次模型,上一步的输出作为下一步的输入。它属于哪一档?
- 想做“自动处理邮件并直接删除垃圾邮件”的 Agent,但没有任何办法判定是否误删。该怎么办?
- 把
agent_min.py里的MAX_STEPS删掉,什么情况下会真的出事?
参考答案
- 不是。路径固定,属于单次调用或 Workflow。需要关注的是写入财务系统前的校验与确认。
- 必须执行的合规步骤不能靠模型自觉,应写进代码的固定节点。
- 提示链,属于 Workflow。
- 既无法验证又不可撤销,应先降低AI自己决定多少的程度:只读或移入隔离区,由人工确认,同时建立评估标准。
- 工具持续失败,或模型始终无法得出结论时,会无限循环,延迟与成本失控。
七、系列地图:一个 Agent 要过哪三关
一个 Agent 从想法到安全上线,要依次过三关。全系列围绕同一个项目 SupportPilot 展开,每篇给它加一项能力:
| 阶段 | 要回答的问题 | 篇目 | SupportPilot 里长出什么 |
|---|---|---|---|
| 设计 | 做什么?怎么搭?边界在哪? | 02–11 | 一页纸设计文档、架构图、工具契约、记忆方案 |
| 开发 | 怎么做稳?怎么证明它好用?怎么上线? | 12–21 | 可运行服务、评估集、监控、部署 |
| 安全 | 被攻击或自己出错时怎么办? | 22–27 | 威胁模型、防护层、审计、红队用例、上线检查表 |
| 毕业 | 能否独立走完全流程? | 28 | 完整仓库与全流程检查表 |
本篇 3.1 节的“安全面”一行,是后面安全篇的伏笔:Agent 的风险,很大程度来自模型可以触达所有被授权的工具。
八、下一篇预告
02|Agent 架构全景图:今天这 50 行代码,在完整的 Agent 里只占一角。下一篇我们把八大模块画成一张图,看看缺了什么:
| 模块 | 本篇 50 行里有吗 |
|---|---|
| 模型 | 有 |
| 工具 | 有(仅 1 个,只读) |
| 编排(循环) | 有(最简形态) |
| 记忆 | 雏形(只有当次消息列表) |
| 规划 | 无 |
| 护栏 | 雏形(只有步数上限) |
| 评估 | 无 |
| 可观测 | 雏形(只有 print) |
其余五个模块要么缺失,要么只有雏形,正是这个系列接下来要一块块补齐的。