1. 为什么 Agent 工程化面试越来越难糊弄了
这两年 Agent 这个词从论文里一路杀到生产环境,招聘市场上的岗位描述也跟着变了味。前两年你只要说“我调过 LangChain、写过 ReAct 循环”,面试官基本就点头了;现在同样一句话甩出去,对面大概率会追问一句:“那你线上跑的时候,工具调用失败率多少?上下文怎么控的?部署在哪、冷启动多久?”——这一串问题下来,能扛住的人其实不多。
我自己带过几个 Agent 相关的项目,也作为面试官面过不少人,最大的感受是:Agent 工程化面试考的不是你会不会写 demo,而是你能不能把一个不确定的系统跑稳、跑便宜、跑得可观测。开发阶段能跑通只是及格线,部署阶段才是真正的分水岭。很多候选人卡就卡在“开发思维”和“工程思维”之间的那道坎上。
这篇东西我想按面试官真实的提问逻辑,把从开发到部署这条链路上最常被问、也最能拉开差距的 5 个关键问题拆开讲。每个问题我都会说清楚:面试官到底想听什么、背后的工程原理是什么、你该怎么组织回答、以及有哪些坑是只有真跑过线上的人才知道的。适合正在准备 Agent 方向面试的人,也适合已经上手但总觉得“哪里不稳”的开发者对照自查。
先给个整体判断:Agent 工程化的核心矛盾,是大模型的不确定性和工程系统对确定性的要求之间的冲突。你后面看到的几乎所有问题——工具调用、上下文管理、状态持久化、可观测性、成本控制——本质上都是在给这个矛盾打补丁。想明白这一点,很多问题的回答思路就顺了。
2. 关键问题一:工具调用(Function Calling)怎么设计才不翻车
2.1 面试官真正在问什么
“你怎么设计工具调用”这个问题,表面在问 API 怎么写,实际在考三件事:工具粒度的划分、参数校验的健壮性、失败后的兜底策略。我见过太多候选人上来就说“我定义了几个 function,模型自己会选”,然后就没有然后了。这种回答在面试官眼里等于“我只跑过 happy path”。
工具调用的本质,是把自然语言意图映射成结构化函数调用。模型负责“选哪个工具、填什么参数”,你的代码负责“校验参数、执行、把结果喂回去”。问题在于,模型填参数是会出错的——类型错、缺字段、幻觉出不存在的枚举值,这些在生产环境里天天发生。
2.2 工具粒度:别把工具设计成“万能函数”
一个常见的错误是把工具设计得太粗,比如搞一个do_everything(action, params),让模型在里面塞各种 action。这样做的后果是:模型选择准确率暴跌,因为选项太模糊;参数校验几乎没法做,因为 params 是个自由结构;日志也没法看,你根本不知道它到底干了啥。
合理的做法是按业务动作切分,一个工具干一件明确的事。比如做订单查询的 Agent,就拆成query_order_by_id、query_orders_by_user、query_order_status三个独立工具,而不是一个query_order(type, value)。粒度细了,模型的意图分类空间变小,准确率自然上去;参数 schema 也能写得很死,校验逻辑清晰。
但也不能细到离谱。我见过有人把“获取当前时间”和“获取当前日期”拆成两个工具,这就属于过度设计,白白增加模型的决策负担。判断标准很简单:如果两个工具在业务语义上永远会被一起考虑,那就合并;如果它们的参数结构完全不同,那就拆开。
2.3 参数校验:永远不要相信模型给的参数
这是我最想强调的一点。模型返回的 JSON 参数,你必须当成不可信输入来处理,跟处理用户表单提交是一个心态。具体要做几层:
第一层是结构校验,用 JSON Schema 或者 Pydantic 这类工具,检查字段是否存在、类型是否正确、必填项有没有缺。第二层是业务校验,比如订单号格式对不对、时间范围是否合理、枚举值是否在允许列表内。第三层是权限校验,这个工具当前这个用户能不能调,别让模型越权。
from pydantic import BaseModel, Field, validator from typing import Literal class QueryOrderParams(BaseModel): order_id: str = Field(..., min_length=8, max_length=32) detail_level: Literal["basic", "full"] = "basic" @validator("order_id") def check_order_id_format(cls, v): if not v.startswith("ORD"): raise ValueError("order_id must start with ORD") return v校验失败怎么办?不要把异常直接抛给模型让它重试,那样容易陷入死循环。正确做法是返回一个结构化的错误信息,比如{"error": "invalid_param", "field": "order_id", "hint": "订单号需以 ORD 开头"},让模型基于这个 hint 重新生成。同时设一个重试上限,比如 2 次,超过就直接走兜底话术。
2.4 失败兜底:给每个工具准备 Plan B
线上环境里,工具执行失败是常态——下游接口超时、限流、返回脏数据。你的 Agent 不能一失败就崩。我的经验是给每个工具配一个降级策略:能重试的重试(带指数退避),不能重试的返回缓存或默认值,实在不行就明确告诉用户“这个操作暂时不可用,请稍后再试”。
注意:重试一定要区分错误类型。网络超时可以重试,参数错误重试一万次也没用,反而浪费 token。建议在工具执行层做错误分类,把
retryable和non_retryable分开处理。
还有一个容易被忽略的点:工具调用的幂等性。如果模型因为超时重试了同一个“创建订单”的调用,你的系统会不会创建两个订单?这类写操作的工具,必须带幂等键,或者在下游做去重。这个问题在面试里提出来,面试官会明显觉得你是有实战经验的。
3. 关键问题二:上下文管理与记忆机制怎么落地
3.1 上下文不是越多越好
很多人对 Agent 的记忆有个误解,觉得“把历史全塞进去,模型就更懂我”。实际上上下文窗口是有限资源,塞得越多,成本越高、延迟越大,而且模型对中间部分的注意力会衰减——这就是所谓的“lost in the middle”现象。我实测过一个客服 Agent,把完整对话历史(平均 30 轮)全塞进去,和只保留最近 6 轮加摘要相比,回答质量几乎没差别,但 token 消耗差了将近 4 倍。
所以上下文管理的核心问题是:在有限的预算内,保留对当前决策最有用的信息。这需要你把记忆分层。
3.2 三层记忆结构
我一般把 Agent 的记忆分成三层来设计:
短期记忆是当前对话的最近若干轮,直接原文保留,保证对话连贯性。一般保留最近 5 到 8 轮,具体看单轮长度。
工作记忆是当前任务相关的关键信息,比如用户已经确认的订单号、已经排除的选项、当前进行到哪一步。这部分我倾向于用结构化的方式存,而不是靠模型从历史里自己找。比如维护一个task_state字典,每轮更新,需要时直接注入到 system prompt 里。
长期记忆是跨会话的用户偏好、历史事实。这部分通常放向量库,按需检索召回,而不是全量注入。
def build_context(session, current_query): recent = session.history[-6:] # 短期 task_state = session.task_state # 工作记忆,结构化 relevant = vector_store.search( # 长期,按需召回 current_query, top_k=3, threshold=0.75 ) return { "recent_dialogue": recent, "task_state": task_state, "recalled_facts": relevant }3.3 摘要压缩的时机和陷阱
当对话轮数超过阈值,就需要把早期对话压缩成摘要。这里有个坑:摘要不能只让模型自由发挥,否则关键信息(订单号、金额、用户明确拒绝的选项)很容易在压缩中丢失。我的做法是摘要时给一个固定的模板,强制保留几类关键实体:
- 用户明确表达的需求和约束
- 已经确认的事实(订单号、时间、金额等)
- 已经排除的选项和原因
- 当前未解决的问题
摘要触发时机也有讲究。不要每轮都摘要,那样成本高且容易累积误差。一般是当 token 数接近窗口的 70% 时触发一次,把最老的一半对话压缩掉。
3.4 面试怎么答这一题
如果面试官问“你怎么管理 Agent 的上下文”,一个能拿高分的回答结构是:先点明“上下文是有限资源,核心是取舍”,然后讲你的分层设计,再举一个具体的压缩或召回策略,最后补一句你踩过的坑(比如摘要丢信息、召回噪声大)。这样既有框架又有细节,还体现了实战。
4. 关键问题三:状态持久化与多轮任务怎么保证不丢
4.1 为什么状态管理是分水岭
单轮问答的 Agent 谁都能写,但真实业务里的 Agent 往往是多轮、长流程、可能中断的。用户今天问了一半,明天接着问;或者任务执行到第三步,服务重启了。这时候状态管理做得好不好,直接决定系统能不能用。
面试里这个问题经常被包装成:“你的 Agent 怎么支持多轮任务?”“服务重启后会话状态怎么办?”——本质都是在考状态持久化。
4.2 状态该存什么、不该存什么
一个常见的误区是把整个对话历史当成状态存。这样做的后果是状态膨胀、恢复慢、还容易和上下文管理打架。我的原则是:状态只存“决策所需的最小事实集”,对话历史单独存。
具体来说,状态里应该有的是:当前任务 ID、任务阶段(比如“待确认”“执行中”“已完成”)、已收集的关键参数、已执行的动作记录、下一步待办。这些是结构化的、可序列化的、和具体对话文本解耦的。
{ "session_id": "sess_abc123", "task_id": "task_789", "stage": "awaiting_confirmation", "collected": { "order_id": "ORD20240101", "refund_reason": "商品破损" }, "executed_actions": ["query_order", "check_refund_policy"], "pending": ["confirm_refund_amount"] }4.3 存储选型:别一上来就上重型方案
状态存哪?我见过有人张口就说“上 Redis 集群 + 分布式锁”,其实大部分场景根本用不上。选型要看规模和一致性要求:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 单机、低并发 | SQLite / 本地文件 | 零依赖,够用 |
| 中等并发、需共享 | Redis / PostgreSQL | 读写快,支持 TTL |
| 高并发、强一致 | 分布式 KV + 事务 | 需要额外一致性保障 |
对大多数中小规模 Agent 应用,PostgreSQL 存状态 + Redis 做会话缓存,这个组合已经能扛住相当的量。关键是状态写入要幂等,同一个 session 的更新用乐观锁或版本号控制,避免并发写覆盖。
4.4 中断恢复:让任务能“接着跑”
状态持久化最终要服务于一个目标:任务中断后能恢复。这要求你的 Agent 执行流程是“可重入”的。具体做法是把一个长任务拆成若干个 checkpoint,每个 checkpoint 完成后落一次状态。恢复时从最后一个 checkpoint 继续,而不是从头再来。
这里有个细节:副作用操作(比如发消息、扣款)必须记录执行状态,恢复时先检查这个动作是否已经执行过,避免重复。这就是前面提到的幂等性在状态层的体现。
提示:面试时如果能把“状态持久化”和“幂等性”“可重入”这几个词串起来讲,会显得你对分布式系统的基本功是扎实的,这在 Agent 岗位里是加分项。
5. 关键问题四:可观测性——线上 Agent 出问题你怎么查
5.1 没有可观测性的 Agent 就是个黑盒
这是我认为最被低估、也最能区分候选人水平的一题。很多人做 Agent 只关注“能不能跑通”,从来不问“跑挂了怎么定位”。等到线上用户投诉“答非所问”,你打开日志一看,只有一行“请求完成”,啥也查不出来。
Agent 的可观测性和传统后端不太一样,因为它的执行路径是模型动态决定的,不是固定的代码分支。所以你需要专门为它设计埋点。
5.2 必须记录的几类信息
我一般要求 Agent 的每次请求至少记录这些:
- 输入:用户原始 query、注入的上下文摘要(不是全文,太长)
- 模型决策:选了哪个工具、参数是什么、模型的原始输出
- 工具执行:调用耗时、返回状态、错误信息
- 最终输出:给用户的回复
- 关键指标:总 token 数、总耗时、工具调用轮数、是否触发兜底
这些信息串起来,才能还原一次完整的执行链路。我习惯给每次请求生成一个 trace_id,所有环节的日志都带上它,排查时一搜就全出来了。
5.3 用 Trace 还原执行链路
Agent 的一次请求往往包含多轮“模型思考 → 工具调用 → 结果回填”,这天然适合用 trace 结构来记录。每一轮是一个 span,span 里包含模型调用和工具调用两个子 span。这样你一眼就能看出:是模型选错了工具,还是工具执行超时,还是结果回填后模型理解错了。
with tracer.start_span("agent_turn") as turn: with tracer.start_span("llm_call") as llm: decision = call_llm(context) llm.set_tag("tool_selected", decision.tool) llm.set_tag("tokens", decision.usage.total) with tracer.start_span("tool_exec") as tool: result = execute_tool(decision.tool, decision.args) tool.set_tag("status", result.status) tool.set_tag("latency_ms", result.latency)5.4 告警指标怎么定
光有日志不够,还得有告警。我一般盯这几个指标:工具调用失败率(超过 10% 要查)、平均轮数(突然变多说明模型在打转)、兜底触发率(升高说明系统在退化)、P99 延迟(用户体验红线)。这些指标按小时聚合,异常时自动告警。
面试里如果被问到“你怎么保证 Agent 线上稳定”,把可观测性这套讲出来,比空谈“我会做测试”有说服力得多。因为测试只能覆盖你想到的情况,而可观测性帮你发现没想到的情况。
6. 关键问题五:部署形态与成本控制怎么权衡
6.1 部署形态:同步、异步还是流式
Agent 的部署形态直接决定了用户体验和成本结构。常见的有三种:
同步阻塞最简单,用户发请求,服务跑完整个 Agent 循环再返回。适合短任务,但一旦工具调用多、耗时长,用户就得干等,体验差。
异步任务是用户提交后立即返回一个任务 ID,后台慢慢跑,用户轮询或通过推送拿结果。适合长流程任务,但需要额外的任务队列和状态管理。
流式输出是边生成边返回,用户能实时看到 Agent 在“思考”和“行动”。体验最好,但实现复杂度高,而且要注意流式过程中的错误处理——流到一半工具失败了怎么办。
我的经验是:面向 C 端的对话类 Agent 优先流式,面向 B 端的批处理任务优先异步。同步只用在内部工具或极短任务上。
6.2 成本结构拆解
Agent 的成本主要来自三块:模型 token 消耗、工具调用开销、基础设施。其中 token 往往是大头,而且最容易被忽视。
token 成本 = 输入 token × 单价 + 输出 token × 单价。输入 token 里,system prompt 和上下文注入占了大头。我见过一个 Agent 的 system prompt 写了 2000 多 token,每轮都带,光这一项就吃掉大量预算。
控制成本的手段我按性价比排个序:
- 精简 system prompt:能删就删,能合并就合并,这是最直接的省钱方式。
- 上下文裁剪:前面讲的记忆分层,本质也是成本控制。
- 模型分级:简单意图分类用小模型,复杂推理才上大模型。
- 缓存:相同或相似的 query 结果缓存,尤其是 FAQ 类场景。
- 限制轮数:给 Agent 循环设一个硬上限,比如最多 8 轮,防止失控。
6.3 一个真实的成本优化案例
我之前优化过一个客服 Agent,初始版本每次对话平均消耗 8000 多 token。做了三件事后降到 2500 左右:把 system prompt 从 1800 token 压到 600;把全量历史改成最近 6 轮加摘要;把意图分类从大模型换成小模型。质量基本没降,成本降了将近 70%。
注意:成本优化一定要有质量评估兜底。每次改动后跑一遍评测集,确认准确率没有明显下降再上线。否则省了钱丢了体验,得不偿失。
6.4 面试怎么答部署和成本
这题的高分回答要体现权衡思维。不要只说“我用流式”,而要说“我根据任务时长和用户场景选了流式,因为对话类场景用户对延迟敏感;同时我用模型分级和上下文裁剪把单次成本控制在 X 以内”。有场景、有选择、有数据,这才是工程化的回答。
7. 面试现场:怎么把这 5 个问题串成一个完整故事
7.1 别把回答做成知识点罗列
前面五个问题单独答好不难,难的是让面试官觉得你真的做过一个完整系统。我的建议是准备一个自己深度参与过的 Agent 项目,把这五个点自然地串进去。比如这样组织:
“我做过一个 XX 场景的 Agent,开发阶段主要解决工具调用和上下文管理,工具按业务动作拆分并做了三层参数校验,上下文用三层记忆结构控制 token。部署阶段重点做了状态持久化和可观测性,状态用 PostgreSQL 存结构化事实,每次请求打 trace 方便排查。成本上通过精简 prompt 和模型分级把单次消耗压到了 X。”
这样一段话,五个关键问题全覆盖,而且有逻辑主线——从开发到部署。面试官顺着任何一个点追问,你都有细节可以展开。
7.2 主动暴露一个坑,比完美更可信
面试里适当讲一个你踩过的坑,反而更真实。比如“我一开始没做工具调用的幂等,结果重试导致重复下单,后来加了幂等键才解决”。这种细节是编不出来的,面试官一听就知道你真跑过线上。
7.3 常见追问速查
| 追问 | 回答要点 |
|---|---|
| 工具调用准确率怎么提升 | 细化工具粒度、优化描述、加 few-shot 示例 |
| 上下文超长怎么办 | 分层记忆、摘要压缩、按需召回 |
| 状态存哪、怎么保证一致 | 结构化存储、版本号乐观锁、幂等写入 |
| 线上问题怎么定位 | trace_id 串联、分 span 记录、关键指标告警 |
| 成本怎么降 | 精简 prompt、模型分级、缓存、限制轮数 |
8. 几个只有真跑过线上才会懂的经验
最后分享几个我在实际项目里踩出来的体会,这些在面试里说出来,往往比标准答案更能打动人。
第一,Agent 的“聪明”和“可控”是一对矛盾。你给它越多自由度,它越可能做出你意想不到的事。生产环境里,我宁愿要一个稍微笨但行为可预测的 Agent,也不要一个偶尔惊艳但经常失控的。所以工具设计、参数校验、轮数限制这些“约束”,不是限制能力,而是保证稳定。
第二,评测集比什么都重要。没有评测集,你所有的优化都是拍脑袋。我每个 Agent 项目都会先攒一个几十到几百条的评测集,覆盖正常场景和边界场景,每次改动都跑一遍。这个习惯让我避免了好几次“以为优化了其实退化了”的事故。
第三,日志要打得“舍得”。很多人怕日志多占空间,结果出问题时啥也查不到。我的原则是:排查时可能需要的信息,一律记。存储成本相比排查成本,根本不值一提。等真出事故了,你会感谢当初多打的那几行日志。
第四,别迷信框架。各种 Agent 框架更新很快,但底层逻辑就那么点东西:循环、工具、上下文、状态。把原理搞懂,用什么框架都能上手;原理不懂,换个框架照样抓瞎。面试官也更看重你对原理的理解,而不是你会不会用某个具体库。
第五,部署不是终点,是起点。上线只是开始,真正的功夫在后面的监控、调优、迭代。一个 Agent 系统能不能长期跑好,取决于你有没有建立起“观测 → 发现问题 → 优化 → 再观测”的闭环。这个闭环能力,才是工程化面试真正想考的东西。