最近在 Hacker News 上刷到一个带着点“审判”意味的讨论:Is “Agentic” Programming a Flop?评论区两边吵得很凶。有人在自己项目里跑 Agent 工作流,预算烧得快,结果却不如一句 if-else;也有人坚持说 Agent 只是被用错了地方,不是这个方向不行。
我把这个争论拿到实际代码里验证了一遍,也动手搭了一个最小可运行的 Agent 工作流。这篇文章不打算站队,而是把 Agentic 编程是什么、为什么有人觉得它“翻车”、到底哪些场景适合用、哪些场景应该绕开,完整梳理一遍。文章里会给出可直接复制的 Python 示例、常见报错和排查思路,以及我在工程落地时的取舍建议。
无论你是刚接触大模型开发的新手,还是已经在生产环境里折腾过 Agent 的后端开发者,这篇文章都能给你一个相对冷静的参考坐标。
1. 背景:Agentic 编程为什么会被质疑是 Flop
1.1 从“大模型能聊天”到“大模型能干活”
过去两年,大家的注意力从“大模型能不能生成一段话”快速转移到了“大模型能不能独立完成一件任务”。于是出现了 Agentic AI、Agentic RAG、Agent 工作流这些热词。
Agentic 编程,通俗地说,就是让大模型不只是一个问答接口,而是一个能自己调用工具、根据工具返回结果调整下一步行动、最终完成任务的“执行者”。它的核心循环可以简化成:
- 接收任务。
- 思考需要调用什么工具。
- 调用工具并拿到结果。
- 根据结果决定继续调用还是给出最终答案。
这套模式在学术界和工业界最著名的范式之一是 ReAct,也就是 Reason + Act,让模型交替进行推理和行动。
1.2 为什么很多人感觉“翻车”了
回到标题里的问题,为什么有人会觉得 Agentic Programming 是 Flop?我翻了大量讨论,负面的声音基本集中在几个真实痛点上:
- 成本不可控:一个简单的查询,Agent 可能反复调用模型好几轮,每一轮都在消耗 token。
- 行为不稳定:同样的任务,这次成功,下次可能在一个小细节上跑偏。
- 调试困难:传统的程序执行路径是确定的,Agent 的执行路径是模型现场“想”出来的,出了问题很难复现。
- 收益不明显:很多任务用几条规则、一个普通的 RAG 流程就能解决,非要上 Agent 反而更慢更贵。
这些批评不是没有道理。但“用错了地方”和“这个东西本身不行”是两码事。这篇文章的后半部分会专门讨论适用边界。
2. 概念拆解:Agent、Agentic RAG 与普通自动化有什么区别
2.1 Agent 到底是个什么东西
在工程语境里,一个 Agent 至少包含三部分:
- 大模型:负责理解任务、做决策。
- 一组工具:模型可以调用的函数,比如查数据库、调 HTTP 接口、读文件。
- 控制循环:决定什么时候继续调用工具、什么时候停止并输出答案。
缺少模型的代码只是普通函数调用;缺少工具的模型只是聊天机器人;缺少控制循环的调用方式则很难处理多步任务。
2.2 Agentic RAG 不是简单的“向量检索 + 大模型”
很多人听到 RAG,第一反应是:把文档切块,做向量化,用户提问时先检索再拼进 Prompt 让模型回答。这是标准的朴素 RAG。
Agentic RAG 则是在这个基础上加入了“多次检索、多次推理”的能力。比如用户问“对比三份合同里付款条款的差异”,朴素 RAG 可能只检索到其中一份文档的片段,而 Agentic RAG 会先检索,发现信息不足,再改写查询继续检索,最后汇总答案。
关键区别在于:Agentic 模式下,检索的次数和查询词不是提前定死的,而是模型根据上一轮检索结果动态决定的。
2.3 Agentic 编程与低代码自动化的边界
另外需要注意,Agentic 编程不等于类似 Python 脚本的自动化。普通自动化是确定的,每一步都提前写死。Agent 的每一步是模型在推理时决定的。这意味着你不可能用单元测试完全覆盖 Agent 的所有执行路径,只能用评估集和边界约束来“限制”和“校验”它的行为。
3. 环境准备与版本说明
这一节我们先搭好运行环境,后面的示例都基于这个环境。
3.1 基础环境
- 操作系统:Windows / macOS / Linux 均可。
- Python:建议 3.10 及以上。
- 大模型 API:下面的示例以 OpenAI 兼容接口为例,你也可以替换为国内可用的兼容服务,只要接口协议一致即可。
3.2 安装依赖
这里最主要的依赖是openaiPython SDK。注意这个 SDK 的版本差异比较大,1.x 版本和 0.x 版本的调用方式完全不同,本文使用的是 1.x 的写法。
python --version pip install openai如果你的网络环境需要走代理,需要确保 HTTP_PROXY 或 HTTPS_PROXY 环境变量配置正确,这里不再展开。
3.3 准备 API Key
下面示例会从环境变量读取OPENAI_API_KEY,不要把 Key 硬编码在代码里。为了安全,我还建议你使用一个专门为测试创建的 Key,并设置调用额度上限。
export OPENAI_API_KEY="你的_key"4. 核心原理:从一次函数调用到一个完整 Agent 循环
4.1 Function Calling:Agent 的“手”
想让大模型具备调用工具的能力,第一步是告诉模型有哪些工具可用。OpenAI 等平台提供了 Function Calling 机制,你只需要把工具描述按指定格式传给模型。
下面是一个最小工具定义,功能是查询天气。请注意,工具描述越清晰,模型越不容易调用错。
# tools.py def get_weather(city: str) -> str: """模拟天气查询""" return f"{city}: 晴,25°C" TOOL_SCHEMA = [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的当前天气", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,如 上海" } }, "required": ["city"] } } } ]这段代码里,description字段很关键。如果工具描述模糊,比如只写“天气查询”,模型可能不会正确从用户输入中提取城市参数。
4.2 Agent 主循环:控制“继续还是停止”
有了工具定义,接下来就是最核心的部分:Agent 控制循环。它的逻辑可以概括为:
- 调用模型,传入历史消息和工具列表。
- 如果模型返回
tool_calls,就执行对应工具,把结果以tool角色消息追加回去,然后继续下一轮。 - 如果模型没有返回
tool_calls,说明它认为可以给出最终答案,循环结束。 - 必须设置最大轮数,避免死循环。
# agent.py import json from openai import OpenAI from tools import get_weather, TOOL_SCHEMA client = OpenAI() def execute_tool(name: str, arguments: str) -> str: """根据函数名执行工具""" args = json.loads(arguments) if name == "get_weather": return get_weather(args["city"]) return f"未知工具: {name}" def run_agent(user_prompt: str, max_steps: int = 5): messages = [ {"role": "system", "content": "你是一个乐于助人的助手,必要时使用工具回答问题。"}, {"role": "user", "content": user_prompt} ] for step in range(max_steps): print(f"--- Step {step + 1} ---") response = client.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=TOOL_SCHEMA, ) msg = response.choices[0].message if msg.tool_calls: messages.append(msg) for tc in msg.tool_calls: print(f"调用工具: {tc.function.name} {tc.function.arguments}") tool_result = execute_tool(tc.function.name, tc.function.arguments) messages.append({ "role": "tool", "tool_call_id": tc.id, "content": tool_result }) continue # 没有工具调用,说明模型已给出最终答案 print("最终答案:", msg.content) return msg.content print("达到最大轮数,强制停止") return None if __name__ == "__main__": run_agent("今天上海天气怎么样?")运行:
python agent.py预期的执行过程大致如下:
--- Step 1 --- 调用工具: get_weather {"city": "上海"} --- Step 2 --- 最终答案: 上海今天晴,25°C。请注意,模型名称、返回格式可能因 API 版本不同而略有差异。如果你用的是其他兼容接口,需要先确认它是否支持tools参数。
4.3 为什么“固定流程”也是一种 Agent
再强调一次:Agent 不一定要多复杂。一个“先检索、再生成”的 Agentic RAG 流程,其实也是在同一套循环里,把“检索”当成一个工具。
# agentic_rag.py def retrieve(query: str) -> str: # 实际项目中这里是向量检索或关键词检索 return "检索到的文档片段:合同付款条款约定在发货后30天内支付。" TOOL_SCHEMA_RAG = [ { "type": "function", "function": { "name": "retrieve", "description": "从知识库中检索与问题相关的文档片段", "parameters": { "type": "object", "properties": { "query": {"type": "string"} }, "required": ["query"] } } } ]把这个工具和前面的主循环拼在一起,就是一个最简版本的 Agentic RAG。它比朴素 RAG 的优势在于,模型可以在第一轮检索结果不够时,自己决定换一个关键词再检索一次。
5. 完整实战:构建一个带“确认机制”的代理任务
5.1 需求背景
下面我们做一个更贴近业务的小例子:一个“带前置确认”的定时任务 Agent。它的任务是:用户告诉它“定期导出报表并发送到指定邮箱”,Agent 需要先调用工具确认报表状态,再决定是否执行发送。在实际生产环境中,类似需求很常见,尤其是涉及写入、发送、删除等操作时,必须引入人工确认。
5.2 项目结构
agent_demo/ ├── tools.py # 工具定义与实现 ├── agent.py # Agent 主循环 └── run_demo.py # 演示入口5.3 带确认机制的实现
我们先定义两个工具:check_report和send_report。
# tools.py def check_report(date: str) -> str: """检查指定日期的报表是否生成完成""" if date == "2025-01-01": return "报表未生成" return "报表已生成" def send_report(email: str) -> str: """发送报表到指定邮箱,高敏感操作,需要确认""" return f"报表已发送至 {email}" TOOL_SCHEMA = [ { "type": "function", "function": { "name": "check_report", "description": "检查报表是否生成,参数为日期", "parameters": { "type": "object", "properties": { "date": {"type": "string"} }, "required": ["date"] } } }, { "type": "function", "function": { "name": "send_report", "description": "发送报表到指定邮箱,必须在确认用户已明确要求后才可调用", "parameters": { "type": "object", "properties": { "email": {"type": "string"} }, "required": ["email"] } } } ]在 Agent 主循环中,我们对send_report做一层人工确认:
# agent.py import json from openai import OpenAI from tools import check_report, send_report, TOOL_SCHEMA client = OpenAI() def ask_user_confirmation(action_desc: str) -> bool: """在新终端或控制台请求用户确认""" answer = input(f"是否确认执行以下操作?[{action_desc}] (y/n): ") return answer.strip().lower() == "y" SENSITIVE_TOOLS = {"send_report"} def execute_tool(name: str, arguments: str) -> str: args = json.loads(arguments) if name == "check_report": return check_report(args["date"]) if name == "send_report": if not ask_user_confirmation(f"发送报表到 {args['email']}"): return "用户取消了本次操作,请勿重试" return send_report(args["email"]) return f"未知工具: {name}" def run_agent(user_prompt: str, max_steps: int = 5): messages = [ {"role": "system", "content": "你是一个报表助手,先检查报表状态,再按用户要求执行发送。"}, {"role": "user", "content": user_prompt} ] for step in range(max_steps): response = client.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=TOOL_SCHEMA, ) msg = response.choices[0].message if msg.tool_calls: messages.append(msg) for tc in msg.tool_calls: result = execute_tool(tc.function.name, tc.function.arguments) messages.append({ "role": "tool", "tool_call_id": tc.id, "content": result }) continue print("最终答案:", msg.content) return msg.content print("达到最大轮数,强制停止") return None运行演示入口:
# run_demo.py from agent import run_agent if __name__ == "__main__": run_agent("请检查 2025-01-01 的报表,如果已经生成,就发送到 boss@example.com")这样,当模型决定调用send_report时,程序会先让用户确认,避免 Agent 在条件判断失误时直接执行高敏感操作。
5.4 运行与验证
python run_demo.py输出会类似:
--- Step 1 --- 调用工具: check_report {"date": "2025-01-01"} --- Step 2 --- 调用工具: send_report {"email": "boss@example.com"} 是否确认执行以下操作?[发送报表到 boss@example.com] (y/n): y 最终答案: 报表尚未生成,但我已尝试发送,结果为:报表已发送至 boss@example.com。这里暴露了一个真实问题:模型可能忽略“先检查再发送”的顺序约束。所以我在执行层加了确认机制,即使模型乱来,操作也不会未经确认就执行。这个思路在生产环境里非常重要,别指望模型“理解”你的意图,要在执行层用代码兜底。
6. 常见问题与排查思路
6.1 Agent 最常见的“翻车”现场
下面我整理了自己和社区里反馈较多的问题,按现象、原因、解决方案列出。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| Agent 不断调用工具,不输出最终答案 | 缺少最大轮数限制,或工具返回结果不明确,模型认为还需要继续 | 设置 max_steps,工具返回内容包含“已结束”“无更多信息”等明确措辞 |
| 每次调用花费大量 token,成本飙升 | 每轮都把完整历史消息传给模型,且工具返回结果过长 | 做上下文裁剪;工具结果只保留关键字段;必要时用摘要替代原文 |
| 模型调用了错误工具 | 工具描述不够精确,或工具之间功能重叠 | 在 description 中写清楚使用条件和典型例子,减少重叠工具数量 |
| 同一任务多次执行结果不一致 | 模型本身有随机性,且没有固定评估标准 | 设置 temperature=0,建立固定评估集,多次采样观察通过率 |
| Agent 在业务系统里产生错误数据 | 执行层缺少校验和确认机制 | 高敏感操作必须加入人工确认;写操作前先做参数校验和权限检查 |
6.2 排查 Agent 问题的通用步骤
如果 Agent 行为异常,不要直接改 Prompt 瞎试,按下面顺序排查:
- 打开日志,确认每一轮模型的输出内容和工具调用参数。
- 手动执行工具,确认工具本身没有 bug。
- 逐步减少工具数量,看模型是否还会出错。
- 用一个固定输入反复跑 5 到 10 次,判断问题是否随机出现。
- 如果错误来自模型理解,再微调描述或给示例,而不是增加更多约束词。
6.3 关于评估集的一点提醒
很多 Agent 项目失败,不是代码写得不好,而是根本不知道“好”的标准是什么。建议第一时间准备一份评估集,包含 20 到 50 条典型输入和预期结果。这样模型或流程调整后,你能快速判断是变好了还是变差了。
7. 最佳实践与工程建议
7.1 先做“确定的事”,再让模型参与决策
我在项目里的一条铁律是:能用规则解决的,绝不让模型决策。Agent 只负责“判断走哪条路”,具体执行尽量走确定性代码。比如判断用户意图后,实际的数据计算仍然用普通函数完成,模型只做调度。
7.2 为 Agent 加上预算和时间约束
生产环境里,每个 Agent 运行实例都应该有成本上限和时间上限。这些约束不是可选优化,而是必须项。
# config.py MAX_STEPS = 5 # 最大调用轮数 MAX_COST_DOLLARS = 0.5 # 单次任务最大花费 REQUEST_TIMEOUT = 30 # 单次请求超时时间调用模型时传入超时参数,并在每轮结束后统计 token 消耗,一旦超过预算立即中止。
7.3 日志记录比任何推理都重要
Agent 的运行过程不可预知,因此必须记录完整轨迹。至少要记录:时间、模型名称、Prompt、模型回复、工具名称、工具参数、工具结果、token 消耗、本轮是否触发停止。
这些日志不仅是排错依据,也是后续评估、安全审计的原材料。
7.4 安全边界最小化
涉及发送邮件、修改数据库、执行命令、通知用户等操作时,务必遵守最小权限原则。在测试环境验证通过后再考虑生产发布。每次变更前做好备份,普通用户角色禁止调用高权限工具。
7.5 用分层结构控制复杂度
建议不要写一个“万能 Agent”。把系统拆成多个小 Agent,每个只负责一个专业领域。比如一个负责检索,一个负责起草,一个负责审核。这样单个 Agent 的 Prompt 更简单,也更容易单独评估和替换。
8. 结论:Agentic 编程到底是 Flop 吗
回到开头的问题。我的判断是:Agentic 编程并没有失败,失败的是“无边界地使用 Agent”这件事。
它不适合解决的问题,主要有三类:
- 完全确定、可以用脚本完成的操作。
- 需要极高准确率和可证明性的事务,比如金融精确计算。
- 你无法提供评估手段的开放场景。
它真正有价值的场景,是那些“需要阅读理解 + 多步决策 + 上下文变化”的任务。包括但不限于:复杂文档对比分析、自动化测试用例生成与执行、多轮检索的资料整合、代码仓库分析与修改建议。
如果你正在考虑落地 Agent,建议从一个小范围、低风险、可以量化的任务开始。先写好评估集,再逐步扩大 Agent 的权限。短期内,Agent 更多是程序员手里的“智能助手”,而不是完全无人值守的“智能员工”。
作为一个参考方向,Agentic RAG 是目前开源社区比较活跃的切入点,你可以在 GitHub 上搜索相关项目,关注它的检索循环设计、评估方式和对工具调用的容错处理,这些都是比纠结“Agent 是不是风口”更实际的问题。