Agent 开发工程师一面面经
完整信息
本篇目录
1. 请做一下自我介绍
2. Python 异步程序中,取消是如何传播的?如何正确实现超时和资源释放?
答案:
asyncio 的取消通过向协程注入 CancelledError 实现。父任务被取消后,不代表所有自行创建的子任务都会自动得到妥善回收;如果直接使用 create_task 后丢失引用,就可能产生孤儿任务。
超时不能只在外层返回错误,还要保证内部网络连接、锁和文件句柄被释放。清理逻辑应放在 finally 中,不能无条件吞掉 CancelledError。Python 3.11 以后可以用 TaskGroup 做结构化并发,其中一个任务异常时,其余任务会被取消并统一等待结束。
import asyncio
async def call_tool(name: str):
try:
await asyncio.sleep(10)
return name
finally:
print(f"release resource: {name}")
async def main():
try:
async with asyncio.timeout(2):
async with asyncio.TaskGroup() as group:
group.create_task(call_tool("search"))
group.create_task(call_tool("database"))
except TimeoutError:
print("request timeout")
asyncio.run(main())
3. Agent 的工具调用如何保证幂等?模型重试时怎样避免重复执行副作用?
答案:
工具调用的重试不能简单地重新请求。对于付款、发消息、创建工单这类有副作用的工具,需要为每次逻辑操作生成稳定的 idempotency_key,通常由会话 ID、任务 ID、工具名和规范化参数共同计算。服务端先查询调用记录,成功过就直接返回原结果,执行中则等待或拒绝重复请求。
调用状态至少要区分 PENDING、RUNNING、SUCCEEDED、FAILED_RETRYABLE 和 FAILED_FINAL。数据库中对幂等键建立唯一索引,并在同一事务内完成状态抢占。对于外部系统,最好把幂等键继续透传;如果对方不支持,就需要通过业务唯一键、Outbox 或补偿任务降低重复副作用的风险。
4. 如何防止 Agent 在工具调用过程中陷入循环?
答案:
不能只设置一个固定的最大轮数,因为同样十轮,有些任务在推进,有些任务只是在重复。更可靠的做法是同时限制总步数、总 token、总费用、单工具调用次数和墙钟时间,并检测连续状态是否发生有效变化。
可以把每轮的工具名、规范化参数、结果摘要和状态版本组成指纹。如果相同指纹连续出现,说明 Agent 可能陷入循环。终止后不应只返回“达到最大轮数”,而应输出已完成步骤、阻塞原因和缺失信息。对于高风险工具,还需要在执行前增加策略检查或人工确认节点。
5. 模型意图识别如何优化,才能兼顾召回率和误触发成本?
答案:
我会把意图识别拆成候选召回、精排和拒识三层。第一层通过规则、关键词或轻量模型召回多个候选;第二层使用大模型结合上下文输出结构化结果;第三层根据置信度、意图间距和风险等级决定执行、澄清还是拒识。
训练和评测不能只看 Accuracy,还要重点观察混淆矩阵、每类 Precision/Recall、拒识准确率以及高风险意图的误触发率。对于边界样本,应通过 hard negative 扩充数据,例如表达非常相似但动作完全不同的请求。线上还需要记录意图版本、提示词版本和最终执行结果,便于回溯错误来自分类、参数抽取还是下游工具。
6. Tool Schema 设计得过细或过粗分别有什么问题?
答案:
Schema 过粗时,一个工具承担太多语义,参数之间存在大量条件依赖,模型很容易生成“格式合法但业务非法”的调用。Schema 过细时,模型需要进行大量连续调用,错误会逐步累积,同时增加延迟和 token 消耗。
合理的工具粒度应对应一个清晰、可验证、最好可幂等的业务动作。枚举值尽量显式,必填字段和互斥关系应在 Schema 或服务端校验中表达,不能全部写在自然语言描述里。返回值也要保持稳定,区分业务失败、系统失败和可重试失败,避免模型只能从一段错误文本中猜测下一步动作。
7. 如何处理大模型流式输出与工具调用之间的状态一致性?
答案:
流式输出只能视为暂态事件,不能把尚未完成的文本直接当成最终消息持久化。一次推理应有独立的 run_id,事件流中携带递增序号,前端按照序号去重和重放。数据库中分别记录模型增量、工具调用请求、工具结果和最终提交事件。
如果模型在流式输出中途决定调用工具,界面可以展示已生成内容,但服务端应等待完整的工具参数并通过 Schema 校验后再执行。连接断开不应直接取消后台任务,除非业务明确要求;客户端重连后可根据 run_id 和最后序号继续消费,最终由 COMPLETED 或 FAILED 事件确定本轮状态。
8. 如何构建一套能够定位 Agent 退化原因的评测数据?
答案:
评测集要保留真实请求分布,同时针对高风险和长尾场景进行过采样。每条样本不能只有一个参考答案,还应标注目标意图、允许调用的工具、关键参数、禁止动作、必要事实、终止条件和可接受的答案范围。
数据应拆分为静态回归集、对抗集、线上抽样集和时间外测试集。涉及检索时,还要分别评价召回、重排、上下文构造和最终回答,否则端到端分数下降时无法定位问题。数据划分要按用户、模板或语义簇去重,避免同义改写同时进入训练集和测试集造成虚高。
9. RAG 系统中,如何区分“没有检索到”和“检索到了错误内容”?
答案:
“没有检索到”通常对应召回不足,应检查切分策略、查询改写、索引覆盖率和召回数量;“检索到错误内容”则更多与语义混淆、权限过滤、时间版本或重排失败有关。两者必须使用不同指标。
可以为测试问题标注相关文档集合,计算 Recall@K、MRR 和 nDCG。如果目标文档根本没有进入候选集,问题在召回;目标文档已进入候选集但排名靠后,问题在重排;正确证据已经位于上下文中但回答错误,问题在生成或提示词。线上还应保存查询改写结果、候选文档 ID、各阶段分数以及最终引用关系。
10. 混合检索中的稠密向量分数与 BM25 分数为什么不能直接相加?
答案:
两种分数不在同一分布和量纲上。向量相似度可能集中在较窄区间,而 BM25 分数会受到词频、文档长度和语料规模影响,直接相加会导致某一路长期占主导。
常见做法是使用 Reciprocal Rank Fusion,只依赖排序名次;也可以在每个查询内部做分位数归一化,再通过验证集学习权重。更进一步可以把 BM25 分数、向量分数、文档新鲜度、权限特征和来源质量作为特征,交给 Learning to Rank 或 Cross-Encoder 重排。融合参数必须在与线上分布相近的数据上确定。
11. 长上下文中出现“Lost in the Middle”时,应该如何处理?
答案:
关键证据放进上下文并不代表模型一定会使用,尤其是证据位于中间、内容重复或存在干扰文档时。处理方式包括先重排和去重,只保留支持回答的最小证据集合;把最关键证据放在开头或接近问题的位置;对长文档先做结构化抽取,再进行生成。
复杂问题可以先分解子问题,分别检索后再聚合。生成阶段要求模型输出引用位置,并通过后处理检查结论是否能被引用内容支持。单纯扩大上下文窗口往往会增加成本和噪声,不能替代检索质量控制。
12. Age
本篇目录
- 01
请做一下自我介绍
- 02
Python 异步程序中,取消是如何传播的?如何正确实现超时和资源释放?
- 03
Agent 的工具调用如何保证幂等?模型重试时怎样避免重复执行副作用?
- 04
如何防止 Agent 在工具调用过程中陷入循环?
- 05
模型意图识别如何优化,才能兼顾召回率和误触发成本?
- 06
Tool Schema 设计得过细或过粗分别有什么问题?
- 07
如何处理大模型流式输出与工具调用之间的状态一致性?
- 08
如何构建一套能够定位 Agent 退化原因的评测数据?
- 09
RAG 系统中,如何区分“没有检索到”和“检索到了错误内容”?
- 10
混合检索中的稠密向量分数与 BM25 分数为什么不能直接相加?
- 11
长上下文中出现“Lost in the Middle”时,应该如何处理?
