Agent 核心原理:用小项目验证核心能力
这篇我按“先跑起来、再讲取舍”的方式写《Agent 核心原理:用小项目验证核心能力》。概念会讲,但重点放在代码怎么组织、哪里容易踩坑。
摘要
本文概述文章目标、核心观点和实践价值。
很多人学 Agent 容易陷入一种误区:上来就搞 LangGraph 的复杂状态机,或者一心想构建一个能自主完成整个软件生命周期的超级 Agent。结果往往是 Demo 跑通两次就崩了,逻辑混乱,调试无门。
我之前的文章里也提过,无论是前端转大模型还是后端进阶,“先跑通最小流程,再谈复杂度”是铁律。今天我不讲那些宏大的理论架构,而是通过一个具体的、可运行的“小项目”——一个能查天气并记录对话历史的个人助手,来拆解 Agent 的三个最核心组件:规划(Planning)、工具调用(Tool Calling)和记忆(Memory)。
这三个模块不是孤立的,它们是互相咬合的齿轮。搞懂它们,你才算真正入了 Agent 的门。
目录
- Agent 的本质:从 Prompt Engineering 到 Action Engineering
- 规划能力:不仅仅是生成文本
- 工具调用:边界感的艺术
- 记忆系统:从 Context Window 到 Vector Store
- 失败恢复:让 Agent 更 robust
- 总结
Agent 的本质:从 Prompt Engineering 到 Action Engineering
在 ChatBot 时代,我们的任务是优化 Prompt,让模型说得更像人。但在 Agent 时代,任务是让模型做事情。
Agent 的本质是一个Perception-Decision-Action循环:
1.感知:接收用户输入和当前环境状态(包括记忆)。
2.决策:LLM 作为大脑,决定下一步做什么(调用工具?继续对话?还是终止?)。
3.行动:执行决策,并将结果反馈回感知环节。
很多初学者觉得 Agent 难,是因为他们试图在一个chat调用里解决所有问题。正确的姿势是将“思考”和“执行”剥离。下面我们通过代码来看看这个剥离过程。
规划能力:不仅仅是生成文本
规划是 Agent 的大脑皮层。它决定了 Agent 是把一个大任务拆解成子步骤,还是直接给出答案。
在我的项目中,我并没有使用复杂的 ReAct 或 Tree of Thoughts 算法模板,而是利用 LLM 原生支持的Function Calling功能来实现简易规划。
误区提醒:不要为了规划而规划。如果用户只是问“北京今天天气怎么样”,不需要规划,直接调用工具即可。只有当任务涉及多步逻辑(如“帮我订一张去北京的机票,然后查一下那里的天气”)时,规划才有价值。
实战案例:状态机的简化实现
我们用 Python 模拟一个极简的 Agent 循环。这里不依赖庞大的框架,直接用openai的 SDK 演示底层逻辑。
import json from openai import OpenAI client = OpenAI(api_key="your-api-key") def get_weather(location): """模拟工具:获取天气""" return f"{location} 今天晴,25摄氏度。" def book_flight(destination): """模拟工具:预订机票""" return f"已为您预订前往 {destination} 的机票,航班号 CA123。" # 定义可用的工具 schema tools = [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的天气", "parameters": { "type": "object", "properties": { "location": {"type": "string", "description": "城市名称"} }, "required": ["location"] } } }, { "type": "function", "function": { "name": "book_flight", "description": "预订指定目的地的机票", "parameters": { "type": "object", "properties": { "destination": {"type": "string", "description": "目的地城市"} }, "required": ["destination"] } } } ] def run_agent_loop(messages): # 第一步:LLM 决定是否需要调用工具 response = client.chat.completions.create( model="gpt-4o", messages=messages, tools=tools, tool_choice="auto" ) message = response.choices[0].message # 如果 LLM 选择了工具调用 if message.tool_calls: # 执行工具并收集结果 new_messages = messages + [message] # 将 LLM 的请求发给模型看 for tool_call in message.tool_calls: function_name = tool_call.function.name function_args = json.loads(tool_call.function.arguments) # 路由到具体函数 if function_name == "get_weather": result = get_weather(function_args['location']) elif function_name == "book_flight": result = book_flight(function_args['destination']) else: result = "Unknown function" # 将工具结果追加到消息历史中 new_messages.append({ "tool_call_id": tool_call.id, "role": "tool", "name": function_name, "content": result }) # 第二步:带着工具结果再次询问 LLM,让它综合回复 final_response = client.chat.completions.create( model="gpt-4o", messages=new_messages ) return final_response.choices[0].message.content else: # 没有调用工具,直接返回回复 return message.content # 测试用例 history = [{"role": "user", "content": "帮我查下上海的天气"}] reply = run_agent_loop(history) print(reply) # 输出: 上海今天晴,25摄氏度。在这个循环里,规划体现在if message.tool_calls:这一分支判断上。LLM 并不直接返回最终答案,而是返回“我想做什么”。这种间接性是 Agent 与普通 Chatbot 的最大区别。
工具调用:边界感的艺术
工具是 Agent 的手脚。但在实际工程中,工具越多,幻觉越严重。
我在维护一个企业内部助手时发现,当工具列表超过 20 个时,GPT-3.5 甚至 GPT-4 都会开始混淆参数,或者调用错误的工具。
我的取舍策略:
1.分组管理:不要在 System Prompt 里塞入所有工具的描述。根据当前用户意图动态注入相关工具的描述。
2.强类型校验:LLM 生成的 JSON 经常格式错误。必须在代码层面对工具输入进行严格的 Pydantic 或 JSON Schema 校验,失败则重试或报错,而不是盲目执行。
3.沙箱隔离:敏感操作(如删除数据)必须有二次确认机制,不能全权交给 LLM。
记忆系统:从 Context Window 到 Vector Store
很多教程一上来就讲 RAG,但对于 Agent 来说,短期记忆(Context Window)和长期记忆(Vector DB)是两码事。
- 短期记忆:就是当前的对话历史。它受限于 Context Window 大小。
- 长期记忆:用于存储用户偏好、历史项目信息、过往的决策逻辑。
坑点:不要把所有历史对话都存入 Vector DB。
如果你把用户过去一年的聊天记录全部向量化,检索时的噪声会极大干扰当前任务。
我的做法:
采用“滑动窗口 + 摘要存储”的策略。
1. 最近 10 轮对话直接放入 Context。
2. 更早的对话生成摘要,存入向量库。
3. 在每次规划前,检索相关的摘要,拼接到 System Prompt 中作为背景信息。
例如,用户在一个月前问过“我的咖啡偏好是加糖”,这个信息不应该出现在当前的聊天上下文中,但当用户现在说“帮我点杯咖啡”时,Agent 应该通过记忆检索知道要加糖。
失败恢复:让 Agent 更 robust
真实环境中,网络会超时,工具会返回 500 错误,LLM 可能会抽风。一个能自我修复的 Agent 才是 Production Ready 的。
我在项目中加入了一个简单的重试机制和错误反馈回路:
# 伪代码逻辑 try: tool_result = execute_tool(tool_func, args) except Exception as e: # 将错误信息作为新的 tool 返回给 LLM error_msg = f"Tool execution failed: {str(e)}" # 告诉 LLM 出错了,让它尝试修正参数或换一种方式 new_messages.append({ "role": "tool", "content": error_msg, "tool_call_id": ... }) # 再次触发 LLM 推理 return run_agent_loop(new_messages)这种做法的核心在于:把错误当作一种信号,而不是终点。LLM 在处理“我刚才那个参数传错了,系统报错了”这类指令时,往往能比人类更快速地修正逻辑。
总结
回到最初的问题,Agent 的学习路线应该怎么走?
1.先写死逻辑:用 Python 写一个简单的 if-else 工具调用脚本,理解 Request/Response 的结构。
2.接入 LLM 规划:把硬编码的路由换成 LLM 的 Function Calling,体验“让模型决定下一步”的感觉。
3.引入记忆:加上向量数据库,实现跨会话的信息提取。
4.增强鲁棒性:加上错误处理、重试机制和并发控制。
不要一上来就追求复杂的 Graph 编排。很多时候,一个简单的线性循环(Loop),配合良好的 Tool Design 和 Memory 策略,足以解决 80% 的业务场景。
Agent 的核心不在于“智能”本身,而在于对不确定性的管理能力。当你能够优雅地处理工具失败、记忆缺失和规划偏差时,你就真正掌握了 Agent 的工程精髓。
资料展示
下面是我整理的AI大模型学习资料和工具包预览,适合收藏后按主题逐步学习。
如果你想看完整资料目录,可以在评论区留言「资料」;也欢迎告诉我你更关注AI大模型里的哪类内容。
