聊《Agent 核心原理:一次新的项目切入》之前,先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。
摘要
先把这篇文章的目标说清楚:看完之后,你应该能判断这件事值不值得做,以及从哪里动手。
最近团队在引入 AI 辅助开发时,发现了一个很有意思的现象:之前大家还在纠结用 Claude Code 还是 Codex,现在讨论的重点已经变成了“怎么让这几个工具在团队里协同工作”。
这其实是一个信号。当 AI 工具从“个人极客玩具”变成“团队基础设施”时,我们不能再把它当成简单的 Chatbot 来用。在之前的几个内部 Demo 中,我见过太多因为过度设计导致的维护灾难:为了一个简单的 CRUD 接口,搭了一套完整的 LangGraph 状态机,结果连 Prompt 版本管理都没搞定,最后不得不回退到手动代码审查。
作为小团队,资源有限,我们需要的不是那种能自动规划宇宙探索路线的通用 Agent,而是一个能稳定理解需求、准确调用工具、并在出错时知道怎么重试的“靠谱搭档”。
今天这篇,我不聊那些宏大的理论,而是结合我们最近重构内部文档助手的项目,拆解 Agent 的三个核心骨架:规划、工具调用、记忆。我会尽量多地讲清楚我们在哪里做了减法,以及为什么这么选。
目录
- Agent 的本质:是系统,不是对话框
- 规划能力:少即是多
- 工具调用:契约大于能力
- 记忆系统:短期够用,长期谨慎
- 失败恢复:让 Agent 学会“认怂”
- 总结
Agent 的本质:是系统,不是对话框
很多人对 Agent 的误解在于,觉得它就是一个更聪明的对话窗口。但在工程视角下,Agent 本质上是一个循环执行的控制系统。
它的核心逻辑很简单:
1. 感知:读取用户意图和历史上下文。
2. 决策:LLM 判断下一步该做什么(是回答问题,还是调用外部 API)。
3. 行动:执行操作,并获取结果。
4. 反思:根据结果更新状态,决定继续循环还是结束。
在小团队落地时,最大的坑就是试图一次性实现所有功能。我的建议是:先跑通“单步调用”,再考虑“多步规划”。
如果连单次调用的准确性和延迟都控制不好,引入复杂的规划器只会让系统变得不可控。我们最初的项目目标是生成 SQL 查询并返回数据,这是一个典型的单步工具调用场景。后来发现查询经常报错,才逐步引入了错误处理和重试机制,这才涉及到了简单的规划逻辑。
规划能力:少即是多
“任务规划”听起来很高大上,但在实际工程中,它往往意味着高昂的 Token 成本和不可预测的执行时间。
对于大多数垂直场景,我们不需要 Agent 去分解“我要做一个电商后台”这样的大任务。相反,我们需要的是基于上下文的即时决策。
什么时候需要规划?
只有当任务具有强依赖关系且中间状态不确定时,才需要规划。比如:先查数据库确认用户是否存在,存在则更新积分,不存在则注册。这种分支逻辑,用传统的代码 If-Else 处理效率更高,LLM 反而容易幻觉。
我们的取舍
在我们的项目中,我们刻意限制了 LLM 的规划深度。我们定义了一套固定的工作流模板:
# 伪代码示例:简化的工作流路由 def agent_router(user_intent, history): # 1. 意图分类由轻量级模型处理,降低 LLM 负担 intent_type = light_classifier.predict(user_intent) if intent_type == "QUERY": return handle_query_flow(user_intent, history) elif intent_type == "ACTION": return handle_action_flow(user_intent, history) else: # 兜底策略:直接返回澄清问题,避免盲目规划 return generate_clarification_question(intent_type)这种“硬编码路由 + 软性执行”的模式,虽然不够灵活,但对于小团队来说,稳定性和可调试性远比所谓的“通用智能”重要。只有当路由规则过于繁琐难以维护时,才考虑引入如 ReAct 或 Plan-and-Solve 这样的规划框架。
工具调用:契约大于能力
工具调用(Function Calling)是 Agent 与现实世界交互的桥梁。这里的关键不在于你能调用多少个 API,而在于工具定义的准确性和参数校验的严格性。
我在审查团队代码时发现,很多开发者喜欢把复杂的 JSON 结构直接塞给 LLM 让其填充。这是极其危险的。LLM 擅长概率生成,不擅长精确的结构化数据构造。
最佳实践:严格的 Schema
务必为每个工具提供清晰的类型定义和描述。以下是我们采用的一个标准模板:
{ "name": "update_user_profile", "description": "Update specific fields of a user's profile. Only provided fields will be updated.", "parameters": { "type": "object", "properties": { "user_id": { "type": "string", "description": "The unique identifier of the user." }, "nickname": { "type": "string", "description": "The new nickname. Must be between 2 and 20 characters.", "maxLength": 20, "minLength": 2 }, "avatar_url": { "type": "string", "format": "uri", "description": "Valid HTTP/HTTPS URL for the avatar image." } }, "required": ["user_id"] } }注意required字段和具体的类型约束(如maxLength,format)。这些约束不仅帮助 LLM 更好地生成参数,更重要的是,它在后续的代码层可以作为一个清晰的校验契约。不要信任 LLM 的输出,永远要在代码层进行二次校验。
记忆系统:短期够用,长期谨慎
记忆是 Agent 显得“聪明”的关键,也是技术债务的重灾区。我们通常将记忆分为短期记忆(Context Window)和长期记忆(Vector Store/RAG)。
短期记忆:清理优于堆砌
很多开发者习惯把所有历史对话都塞进 Context。结果就是 Token 爆炸,推理变慢,且噪音干扰严重。
我们的做法是滑动窗口 + 摘要压缩。当对话超过一定长度,或者进入新的话题阶段,我们会触发一个摘要模块,将之前的关键结论浓缩成几条 Bullet Points,保留在当前 Context 中。
# 记忆清理策略示例 def compress_memory(history, current_step): if len(history) > MAX_HISTORY_LIMIT: # 提取关键实体和决策点 key_points = extract_key_decisions(history) # 丢弃无关的闲聊和重复查询 compressed = summarize(key_points) return [compressed] + history[-SLIDING_WINDOW_SIZE:] return history长期记忆:引入成本需评估
对于大多数非专业领域的小团队应用,RAG(检索增强生成)往往是过杀。如果你的业务逻辑主要是基于规则或结构化数据,传统的数据库查询比向量搜索更准确、更快。
只有在涉及大量非结构化文档(如合同、技术手册、客服记录)时,才值得投入精力搭建向量数据库。否则,维护 Embedding 管道的成本远高于它带来的收益。
失败恢复:让 Agent 学会“认怂”
最后,也是最重要的一点:Agent 必须能够优雅地失败。
在实际项目中,LLM 可能会产生幻觉,工具调用可能超时,网络可能抖动。一个健壮的 Agent 系统,应该具备明确的错误处理机制,而不是无限重试直到崩溃。
我们设定了三层防御:
1. 输入校验:在发给 LLM 之前,先检查参数合法性。
2. 输出校验:LLM 生成工具调用后,先校验参数格式,不符合则要求 LLM 重新生成。
3. 人工介入:当连续重试失败超过阈值,或者涉及敏感操作(如删除数据),暂停自动执行,通知人工审核。
总结
回到开头的话题,AI 编程工具走向团队协作,意味着我们要从“玩票”心态转向“工程”心态。
Agent 的核心原理并不复杂,难的是如何在有限的资源下,做出正确的取舍。
- 规划上,能用规则就不用 LLM 推理;
- 工具上,严格定义 Schema 胜过千言万语的描述;
- 记忆上,保持上下文精简,按需引入长期存储;
- 容错上,设计好失败路径比追求成功率更重要。
不要为了用 Agent 而用 Agent。如果你的问题可以用一个简单的 API 调用解决,那就不要引入 Agent 框架。技术的选择永远服务于业务的稳定性与效率。希望这篇基于实战的拆解,能帮你在下一个项目中避开那些“看起来很酷,用起来很痛”的坑。
资料展示
下面是我整理的AI大模型学习资料和工具包预览,适合收藏后按主题逐步学习。
如果你想看完整资料目录,可以在评论区留言「资料」;也欢迎告诉我你更关注AI大模型里的哪类内容。