1. 为什么“从面经开始”反而是学 Agent 开发的一条捷径
很多人一听到“Agent 开发”这四个字,第一反应是去啃论文、翻官方文档、从零搭一个 ReAct 循环。我一开始也是这么干的,结果两周下来,脑子里全是概念,手上一个能跑的东西都没有。后来我换了个思路——从面经倒着学。面经里反复出现的那些问题,其实就是这个领域真正的核心考点,也是实际开发中最容易踩坑的地方。你顺着这些问题去补,学到的不是“知识”,而是“能干活的能力”。
这个思路的本质是以输出倒逼输入。面经问题天然带着筛选属性,能出现在面试里的,一定是被行业反复验证过的关键点。比如“Agent 和 Workflow 的区别是什么”“工具调用失败怎么处理”“多轮对话里怎么管理上下文”,这些问题背后对应的都是真实工程里绕不开的坎。你带着问题去学,效率比漫无目的地读文档高得多。
这篇文章适合三类人:一是想转行做 Agent 开发但不知道从哪下手的初学者;二是已经会调 API 但一写复杂逻辑就乱的开发者;三是准备面试、想把零散知识串成体系的人。我会把面经里高频出现的考点拆开,配上我实际写代码时的思路、参数选择和踩过的坑,尽量让你看完就能动手复现。
需要先说明一点:Agent 开发这个方向变化很快,不同团队的技术栈差异也大。我下面讲的是基于常见工程实践总结出来的一套通用打法,不是唯一解,但能帮你建立一个稳定的起点。
2. 面经高频考点拆解与背后的真实需求
2.1 Agent 到底是什么:别被定义绕晕
面经里第一个高频问题就是“你怎么理解 Agent”。标准答案通常会提到“感知、决策、行动”三要素,或者搬出“自主性、反应性、社会性”这类学术定义。但面试官真正想听的,是你有没有把它和普通程序区分开的能力。
我的理解很朴素:普通程序是你写死流程,Agent 是它自己决定下一步做什么。你给它一个目标,它自己拆解、自己选工具、自己判断什么时候停。这里的关键词是“自己决定”。一旦你承认这一点,就会立刻意识到两个工程难题:第一,它怎么知道有哪些工具可用;第二,它怎么保证不跑偏。这两个问题几乎贯穿了所有 Agent 开发的核心工作。
所以我在实际项目里,从来不把 Agent 当成一个“更聪明的函数”,而是当成一个需要约束的自主执行体。约束的手段包括:工具描述要写清楚、系统提示词要限定边界、循环次数要设上限、关键节点要加人工确认。这些手段在面经里往往以“你怎么保证 Agent 稳定性”的形式出现,本质是同一个问题。
2.2 Agent 与 Workflow 的边界:什么时候该用哪个
这是面经里区分度很高的一道题。很多人答“Agent 更灵活,Workflow 更可控”,这话没错但太浅。我实际做项目时的判断标准是:流程是否可枚举。
如果一件事的步骤是固定的,比如“收到邮件→提取关键信息→写入表格→发送通知”,那这就是 Workflow,用代码串起来最稳,别硬套 Agent。反过来,如果步骤取决于中间结果,比如“根据用户问题决定查数据库还是查文档,查完再决定要不要二次检索”,这种分支无法提前写死,才轮到 Agent 上场。
我踩过的一个坑是:早期为了“显得高级”,把一个本来很固定的数据处理流程做成了 Agent,结果它每次选的工具顺序都不一样,调试成本翻倍,最后又改回了 Workflow。所以我的经验是——能用 Workflow 解决的就别用 Agent,Agent 是用来处理不确定性的,不是用来炫技的。面经里问这个,其实是在筛你有没有工程判断力。
2.3 工具调用:Agent 的手和脚
工具调用是 Agent 开发里最核心也最容易出问题的环节。面经里常问“你怎么设计工具”“工具调用失败怎么办”。我先说设计。
工具的本质是给模型一个它能理解的接口。这里有个反直觉的点:工具的功能实现得好不好,往往不是最关键的,工具的描述写得好不好才是。模型是根据你给的名称、描述、参数说明来决定调不调的。我见过太多人把工具描述写成“查询数据”,模型根本不知道查什么数据、什么时候该查。正确的写法应该像给新人写说明书:“根据用户提供的订单号查询订单状态,返回状态码和预计送达时间,适用于用户询问物流进度时”。
参数设计也有讲究。能用枚举就别用自由文本,能拆成多个小工具就别做一个万能工具。我一般会把工具控制在 5 到 10 个之间,太多了模型容易选错,太少了又不够用。下面这张表是我总结的工具设计检查清单:
| 检查项 | 合格标准 | 常见错误 |
|---|---|---|
| 工具名称 | 动词+名词,语义明确 | 用 query、do 这种模糊词 |
| 功能描述 | 说明用途、适用场景、返回内容 | 只写一句“查询信息” |
| 参数定义 | 类型明确,枚举优先 | 全用 string,不给取值范围 |
| 错误处理 | 返回结构化错误信息 | 直接抛异常或返回空 |
| 数量控制 | 单轮可用工具 5-10 个 | 一次性塞几十个工具 |
2.4 上下文管理:Agent 的记忆难题
面经里“多轮对话怎么管理上下文”几乎是必问。这个问题的难点在于,模型的上下文窗口是有限的,而 Agent 执行任务时产生的中间信息又特别多。你不可能把所有历史都塞进去,那样既贵又慢,还会让模型抓不住重点。
我的做法是分层管理。第一层是系统提示词,放角色设定和硬性规则,这部分永远保留。第二层是任务状态,用一个结构化的对象记录当前目标、已完成步骤、待办事项,每次调用时序列化进去。第三层是原始对话历史,只保留最近若干轮,更早的做摘要压缩。这样既控制了 token 消耗,又保证了关键信息不丢。
这里有个实操技巧:摘要不要用模型生成,尽量用规则提取。比如把“已完成的工具调用及结果”直接结构化存下来,比让模型写一段自然语言摘要更可靠,也更省 token。模型摘要容易丢细节,还可能引入幻觉。这个点在面经里很少被问到,但实际项目里非常关键。
3. 从零搭一个最小可用 Agent 的完整实操
3.1 环境准备与技术选型
先说选型。语言我用 Python,生态最全。模型接口层,我建议先直接用官方 SDK,别一上来就上框架。原因很简单:框架会帮你隐藏很多细节,而学 Agent 开发恰恰要理解这些细节。等你把裸调 API 的流程跑通了,再上框架提效也不迟。
依赖方面,最小集合是:模型 SDK、一个 HTTP 请求库、一个用于校验参数的工具库。如果你要做检索类 Agent,再加一个向量库。我个人的习惯是先用最少的依赖把主流程跑通,遇到重复劳动再抽象。
目录结构我一般这样组织:
agent-demo/ ├── main.py # 入口,负责循环调度 ├── tools.py # 工具定义与实现 ├── memory.py # 上下文管理 ├── config.py # 模型参数、提示词 └── requirements.txt这个结构的好处是职责清晰。main 只管循环,tools 只管工具,memory 只管状态。后面要换模型、加工具、改记忆策略,都只动一个文件。
3.2 核心循环的实现思路
Agent 的核心就是一个循环:把当前状态发给模型 → 模型返回动作 → 执行动作 → 把结果塞回状态 → 再发给模型。听起来简单,但每一步都有细节。
第一步,构造请求。系统提示词里要写清楚:你是一个 Agent,可以用哪些工具,什么时候该结束。结束条件一定要明确,比如“当你认为任务完成时,调用 finish 工具并给出最终答案”。没有明确的结束信号,模型可能无限循环。
第二步,解析模型返回。模型可能返回文本,也可能返回工具调用请求。你要判断:如果是工具调用,就执行;如果是纯文本且没有结束信号,就把它当作中间思考,继续循环。
第三步,执行工具。这里必须加异常捕获和超时控制。工具可能报错、可能卡住,不能让整个 Agent 挂掉。我的做法是给每个工具调用包一层,出错就返回结构化的错误信息给模型,让它自己决定重试还是换方案。
第四步,更新状态。把工具结果追加到消息列表,同时更新任务状态对象。然后检查循环次数,超过上限就强制结束,返回当前最好结果。
下面是一个简化的循环骨架,重点看注释里的判断逻辑:
def run_agent(user_input, max_steps=10): messages = build_initial_messages(user_input) state = {"goal": user_input, "steps": [], "done": False} for step in range(max_steps): response = call_model(messages, tools=TOOL_SCHEMAS) if response.has_tool_call: tool_name = response.tool_name tool_args = response.tool_args # 关键:工具执行必须包异常 try: result = execute_tool(tool_name, tool_args) except Exception as e: result = {"error": str(e), "hint": "请检查参数或换用其他工具"} messages.append(response.raw) messages.append(build_tool_result(tool_name, result)) state["steps"].append({"tool": tool_name, "result": result}) if tool_name == "finish": state["done"] = True break else: # 纯文本,当作思考过程继续 messages.append(response.raw) return state这段代码里最值得说的是max_steps。我一般设 8 到 12,具体看任务复杂度。设太小任务做不完,设太大浪费 token 还可能跑偏。这个值需要根据你的实际场景调,没有标准答案。
3.3 工具定义的具体写法
工具定义我推荐用 JSON Schema,因为主流模型接口都支持这个格式。一个合格的工具体验是这样的:
TOOL_SCHEMAS = [ { "name": "search_knowledge", "description": "在内部知识库中搜索信息。当用户询问产品功能、操作步骤、常见问题时使用。返回最相关的三条内容及来源。", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词,尽量具体,比如'退款流程'而不是'退款'" }, "top_k": { "type": "integer", "description": "返回结果数量,默认3,最大5", "enum": [1, 2, 3, 4, 5] } }, "required": ["query"] } } ]注意 description 里我特意写了“什么时候用”和“返回什么”。这两点模型非常依赖。参数里top_k用了 enum,就是为了防止模型传个 100 进来。这些细节看着小,但能显著降低出错率。
3.4 上下文与状态管理的落地
前面说了分层管理,这里给个具体实现。我用一个字典来存状态:
class AgentMemory: def __init__(self, max_recent=6): self.system_prompt = "" self.task_state = {} self.recent_messages = [] self.max_recent = max_recent def add_message(self, msg): self.recent_messages.append(msg) if len(self.recent_messages) > self.max_recent: # 超出部分做规则化压缩,不调模型 old = self.recent_messages.pop(0) self._compress(old) def _compress(self, msg): # 只保留工具名和结果摘要,丢弃原始长文本 if msg.get("role") == "tool": summary = { "tool": msg["name"], "result_preview": str(msg["content"])[:100] } self.task_state.setdefault("history", []).append(summary) def build(self): return [ {"role": "system", "content": self.system_prompt}, {"role": "system", "content": f"当前任务状态:{self.task_state}"}, *self.recent_messages ]这个实现的关键是_compress不调模型,纯规则截断。这样既省成本又稳定。max_recent我一般设 6 到 10,对应大概 3 到 5 轮完整交互。任务状态单独作为一条 system 消息注入,保证模型始终能看到全局进展。
4. 实操中真正会遇到的坑与排查方法
4.1 模型不调用工具怎么办
这是新手最常遇到的问题。你明明定义了工具,模型却只顾着聊天。原因通常有三个:一是工具描述太模糊,模型不知道什么时候用;二是系统提示词没强调“优先使用工具”;三是问题本身确实不需要工具。
排查顺序我建议这样:先看工具描述,把“什么时候用”补上;再看系统提示词,加一句“当问题涉及具体数据时,必须先调用工具获取,不要凭记忆回答”;最后看用户输入,如果确实是个闲聊问题,那不调工具反而是对的。
我遇到过一个典型案例:工具叫get_info,描述是“获取信息”。模型十次有八次不调。改成get_order_status,描述写“根据订单号查询物流状态,用户问'我的快递到哪了'时使用”,调用率立刻上来了。工具名和描述的信息量,直接决定模型的调用意愿。
4.2 工具调用参数错误怎么处理
模型传错参数很常见,比如该传数字传了字符串,该传枚举传了别的值。我的处理原则是不直接报错,而是把错误信息结构化返回给模型,让它自己修正。
具体做法是在工具执行层做参数校验,校验失败就返回:
{ "error": "参数 top_k 必须是 1-5 的整数,你传的是 'many'", "suggestion": "请重新调用,top_k 传 3" }模型收到这种信息,下一轮通常能自己改对。如果你直接抛异常中断循环,任务就失败了。这个设计在面经里经常被问到,本质是考察你有没有把模型当成一个“会犯错的协作者”来设计系统。
4.3 循环停不下来怎么办
Agent 陷入死循环是另一个高频问题。表现是它反复调用同一个工具,或者在不同工具之间来回横跳。原因一般是:任务目标不清晰、工具返回的结果没有推进任务、或者缺少终止条件。
我的应对有三招。第一,在系统提示词里明确终止条件,比如“当你已经获得足够信息回答用户时,调用 finish”。第二,检测重复调用,如果连续两次调用同一工具且参数相同,就注入一条提示“你刚刚已经调用过这个工具,结果是X,请基于此继续或换方案”。第三,硬性步数上限,到了就强制结束并返回当前状态。
下面这张表是我整理的常见问题速查:
| 现象 | 可能原因 | 排查动作 | 解决手段 |
|---|---|---|---|
| 不调工具 | 描述模糊/提示词没强调 | 检查工具 description | 补场景说明,加优先调用指令 |
| 参数错误 | 类型/范围不明确 | 看 schema 定义 | 加 enum、加类型说明、结构化报错 |
| 死循环 | 无终止条件/结果无推进 | 看调用序列 | 加重复检测、加步数上限 |
| 结果跑偏 | 上下文太长/目标丢失 | 看注入的状态 | 压缩历史、强化任务状态注入 |
| 响应太慢 | 工具串行/上下文过大 | 看耗时分布 | 并行化独立工具、精简上下文 |
4.4 几个容易被忽略的实操心得
第一个心得:日志要记全。Agent 的调试比普通程序难,因为它的决策路径不固定。我习惯把每一轮的输入消息、模型返回、工具调用、工具结果全部落盘。出问题时回放日志,一眼就能看出是哪一步跑偏的。没有日志,你只能靠猜。
第二个心得:先用手动模式验证工具。在接入 Agent 之前,我会先手动调用每个工具,确认它们本身是好的。工具本身有 bug,却让模型背锅,是最浪费时间的排查方向。
第三个心得:提示词要版本化。系统提示词改一个字,Agent 行为可能大变。我一般把提示词存在单独文件里,每次改动记一笔,出问题能快速回滚。这个习惯在多人协作时尤其重要。
第四个心得:别追求一次到位。Agent 的行为是概率性的,同样的输入两次结果可能不同。我的做法是先让它跑通 80% 的常见情况,剩下的边缘情况通过加规则、加工具、加校验逐步收敛。想一步做到 100% 稳定,只会把自己耗死。
5. 从能跑到好用:进阶优化方向
5.1 工具并行化提升响应速度
当 Agent 需要调用多个互不依赖的工具时,串行执行会很慢。比如同时查天气和查日程,这两个没有先后关系,完全可以并行。实现上就是把工具调用收集起来,用并发的方式执行,再统一返回结果。
但要注意,并行只适用于无依赖的工具。如果第二个工具的输入依赖第一个的输出,那就必须串行。判断依赖关系可以靠人工标注,也可以让模型在调用时说明。我一般对高频工具对做人工标注,简单可靠。
5.2 引入反思机制提升成功率
反思机制说白了就是让 Agent 在失败后自己复盘。具体做法是:当工具连续失败两次,或者任务超时,就注入一条提示“你之前的方案没有成功,请分析原因并换一种思路”。这个机制能显著提升复杂任务的成功率。
不过反思也有代价,它会增加 token 消耗和响应时间。我的经验是只在失败路径上启用,正常路径不加,这样既提升了鲁棒性,又不影响常规性能。
5.3 评测体系的搭建
Agent 做出来容易,做好难。难就难在怎么衡量它好不好。我的做法是建一个小型评测集,包含 20 到 50 个典型任务,每个任务标注期望的工具调用序列和最终结果。每次改动提示词或工具后,跑一遍评测集,看通过率变化。
这个评测集不需要很大,但一定要覆盖你的核心场景和已知的坑。它最大的价值是防止你改好一个场景、弄坏另一个场景。没有评测集,优化就是盲人摸象。
5.4 成本与延迟的平衡
Agent 的每一步都要调模型,成本是普通对话的好几倍。控制成本的手段有几个:一是精简上下文,前面说的分层管理就是干这个的;二是用小模型做简单决策,大模型做复杂推理;三是缓存高频工具的结果。
延迟方面,除了并行化,还可以考虑流式输出。让用户先看到 Agent 的思考过程,体感上会快很多。这个在交互式场景里特别有用。
6. 关于学习路径的一点个人体会
回到“从面经开始学”这个起点。我自己的路径是:先把面经里的高频问题列出来,每个问题逼自己写一段能跑通的代码去回答。比如问“上下文怎么管理”,我就真的去实现一个分层记忆;问“工具失败怎么办”,我就真的去写异常处理和结构化报错。这样学下来,每个知识点都是带代码的,不是飘在空中的概念。
面经的价值不在于背答案,而在于它帮你划出了重点。Agent 开发这个方向,核心就那么几块:循环调度、工具设计、上下文管理、错误处理、评测优化。你把这几块吃透,剩下的都是组合和调优。
最后分享一个我一直在用的小技巧:每学一个新概念,就把它加进你的最小 Agent 里跑一遍。不要单独写 demo,就在同一个项目上迭代。这样你的项目会越来越完整,知识也会越来越成体系。等面试官问你“做过什么”的时候,你拿出来的不是一个玩具,而是一个真正跑过、调过、踩过坑的系统。这比任何标准答案都有说服力。