AI Agent 应用开发工程师面经
完整信息
本篇目录
1、介绍一下你做的 Agent 项目,你主要负责哪些部分?
这个项目解决的是售后人员查资料和整理工单需要反复切换系统的问题。用户可以问设备故障的处理方法,助手从手册里找依据;如果需要进一步处理,就读取设备和历史工单信息,整理一份工单草稿,由用户确认后创建。
我主要负责两部分:一是文档解析、检索和证据组装,二是把设备查询、工单查询与草稿创建封装成模型可调用的工具,并管理执行状态。模型通过网关接入,我没有参与基础模型训练。
这里的 Agent 不只是生成一段回答:它会根据问题判断是否需要查资料、补充信息或调用工具,再根据返回结果决定下一步。但权限检查、参数校验和创建前确认由程序控制,模型不能自行绕过这些步骤。项目的核心是让自然语言理解与确定性的后端逻辑配合,而不是把整个业务交给模型自由执行。
2、你刚才提到查手册。为什么用 RAG,而不是把手册拿去微调模型?
RAG 是先从外部知识库找到相关资料,再把资料作为上下文交给模型生成答案;微调则通过训练更新模型参数,让模型更适应某类任务、输出方式或领域表达。两者解决的问题有重叠,但更新机制和可追溯性不同。
我们这里的手册会更新,同一型号又有多个固件版本。用 RAG 可以更新索引,并把具体来源展示给用户;如果把知识主要放进模型参数,很难准确控制某次回答用了哪个版本,也不方便删除过期事实。因此首期优先做检索。
微调仍可能有价值,例如模型总把特定业务术语理解错,或者经过提示与工具设计后仍不能稳定完成任务。但要先确认问题在模型行为,而不是资料根本没检索到。微调也不意味着模型会准确记住所有事实,RAG 也不意味着不会产生幻觉,实际可以组合使用。
3、RAG 里的文档一般怎么切分?chunk 越大是不是越容易保留完整信息?
块大通常能保留更多上下文,但也会混入无关内容,影响向量表达和生成时的信息利用;块小更聚焦,却可能把条件与结论拆开。所以 chunk 大小不是越大或越小越好,要看文档结构和问题需要什么证据。
在这个项目里,我会先沿章节、段落和表格切分,保存型号、固件版本、标题路径和来源位置。故障表拆成几组行时,每组都要带上表头、单位和适用条件。否则检索到“重启设备”这句话,却丢掉“仅适用于某版本”的限制,回答就可能错。
重叠窗口能缓解普通文本的边界截断,但不能保证跨页注释被带上,也会增加重复内容。必要时用较短片段检索,命中后再补充有限的父段落。方案比较应看能否找全回答所需的条件和操作步骤,而不只看平均片段长度。
4、Embedding 是什么?余弦相似度和内积有什么区别?
Embedding 是把文本映射到向量空间,让相关表达在所采用的相似度下更接近。它的效果取决于模型训练目标与数据,不能理解成每个坐标都对应一个明确的业务概念,也不是向量接近就保证事实相同。
内积是 q·d,受方向和长度共同影响;余弦相似度是 (q·d)/(‖q‖‖d‖),主要比较方向。对于固定的非零查询,如果所有文档向量的范数相同,两者排序一致;查询与文档都做 L2 归一化时,余弦就等于内积。零向量不能直接代入这个除法。
举个例子,q=(1,0),d1=(2,2),d2=(1,0)。内积把 d1 排前面,余弦却把 d2 排前面。所以建库时要遵循模型要求的度量与预处理,查询时也保持一致,不能只因为某种相似度更熟悉就随意切换。
5、Function Calling 的完整流程是什么?在你的项目里怎么用?
应用先把可用工具的名称、用途和参数结构告诉模型。模型根据问题返回要调用的工具及参数;应用解析、校验并执行工具,把结果按调用标识交还模型,模型再继续回答或发起后续调用。函数实际由应用执行,不是模型输出一个函数名就自动获得了执行权限。
例如用户问某台设备有没有未处理工单,模型可以请求工单查询工具。后端从登录身份确定可查询范围,校验设备标识,调用业务服务,再把工单状态返回给模型。若设备信息不明确,就先补充信息,不能编造一个 ID 去调用。
工具结果也要表达失败状态:查不到记录、没有权限和服务超时是不同情况。模型知道这些差别,才能决定澄清、说明限制或稍后重试。创建草稿还多一层用户确认,不能把读取信息的授权当作写入授权。
6、有了 JSON Schema,是不是就不会出现工具调用错误了?
Schema 主要约束结构,比如必填字段、类型、枚举和是否允许额外字段。它有助于发现少字段、类型错这类问题;具体生成是否严格受约束,还取决于所用接口和配置,后端仍然要校验。
即便结构完全合法,也可能业务错误。例如设备 ID 是一个合法字符串,却不属于当前用户;工单类型是合法枚举,但与故障不匹配。Schema 不能替代授权、资源归属检查和业务规则。
所以我会分三层:先检查数据能否解析、是否符合结构,再检查业务含义与权限,最后才执行。参数值用于数据库查询时使用绑定参数,也不能因为来自模型就直接拼接。校验失败应返回具体、可修正的信息,但不能把内部凭据或敏感数据库错误一起暴露给模型。
7、我们问一点模型基础。Attention 怎么计算,为什么要除以 √d_k?
缩放点积注意力的形式是 Attention(Q,K,V)=softmax(QKᵀ/√d_k)V。Q 可以理解为当前位置要寻找什么,K 用来与查询匹配,V 是按匹配权重汇总的信息。实现中它们是由输入经过不同投影得到的向量,并不是三份人工定义好的知识。
除以 √d_k 是为了控制点积的尺度。在分量近似独立、均值为零、方差为一的简化假设下,q·k 的方差随 d_k 增大;缩放后方差约保持在同一量级,避免 softmax 因输入绝对值过大而过度尖锐、梯度变小。这是解释设计动机的近似推导,不是任意训练状态下都严格成立的分布假设。原始论文 Attention Is All You Need
还要区分 Attention 与检索相关性:注意力权重高不等于某条事实可靠,它只是当前计算中的信息分配。因此把正确文档放进上下文是必要条件之一,不能据此保证模型一定准确采用它。
8、自回归语言模型训练时可以并行计算,为什么生成答案通常还要逐 token 进行?
常规自回归训练的目标是根据前缀预测下一个 token。训练样本中的目标序列已经给定,可以把真实序列右移作为输入,并用因果 mask 限制每个位置只能使用它前面的内容。这样一轮前向计算就能得到多个位置的预测损失,计算上并行,但信息依赖仍然遵守从前到后的方向。
普通逐 token 解码时,后面的真实 token 还不存在。必须先选出当前 token,才能把它作为后续条件,因此生成阶段有顺序依赖。输入提示词已经全部已知,其预填充阶段通常可以并行处理;这与生成输出的逐步解码是两回事。
工程上可以通过缓存、批处理或推测解码改善吞吐与延迟,但不能因此说自回归依赖消失了。对应用开发来说,这也解释了为什么输出长度会明显影响完成时间,以及“首个字出来很快”和“整段答案生成很快”不是同一个指标。
9、你的检索会同时用关键词和向量吗?为什么需要两路,结果又怎么合并?
会。关键词检索擅长精确标识和词项匹配,向量检索更适合处理同义表达与语义相关。故障码 E104 和 E140 看起来相近,处理方法可能完全不同;而用户说“机器热了就停”,手册可能写“过温保护”,这时只靠字面又容易漏掉。
关键词通道通常可以用 BM25,它结合词频、逆文档频率和文档长度调整;故障码、型号还应有合适的精确字段。两路都要遵守型号和权限范围,不能让不适用文档参与竞争。Elastic 排序说明
合并时不宜直接把原始分数相加,因为分数尺度和分布不一样。我们可以采用 RRF,用各路排名贡献累加分数;也可以对分数校准后做加权融合。RRF 实现简单,代价是忽略原始分数间距。我会通过精确标识、口语描述等不同查询类型判断组合是否有增益,而不是认为两路一定比一路好。
10、召回后为什么还要重排?把召回数量一直增大,会不会效果一直提高?
双编码器召回分别编码问题和文档,文档向量能预计算,因此适合大范围快速找候选。交叉编码器重排把问题与候选一起计算,可以更细地比较条件、否定词和对象,但每个候选都要随当前问题重新计算,成本更高。Sentence Transformers 检索与重排文档
在售后场景里,两段内容都包含同一个故障码,可能分别对应升级前后。重排有机会更准确地区分,但前提是候选里有正确证据。如果召回只找到别的版本,重排不能凭空补回。
增加候选数量可能提高证据进入候选集的机会,也会增加重排耗时,甚至引入更多高相似但不适用的内容。还要区分“送去重排的数量”和“最终送给生成模型的数量”。我会在固定时间预算下画出候选规模与必要证据覆盖、最终排序和答案质量的关系;后段指标已经不涨,就不能只为了召回率继续扩大规模。
11、写一个 RRF 融合函数。输入是多路排好序的文档 ID,说明重复 ID、并列和复杂度怎么处理。
RRF 对同一文档在每一路的排名计算 1/(c+rank),再累加。排名从 1 开始,没有出现的列表贡献 0。下面约定 ID 为字符串,同一路重复 ID 只按首次出现计分,去重后排名连续;并列按 ID 排序,使输出确定。RRF 公式
from collections import defaultdict
from math import isfinite
def rrf(rankings, top_k=5, c=60):
if not isinstance(top_k, int) or top_k < 0:
raise ValueError("top_k must be a nonnegative integer")
if not isfinite(c) or c <= 0:
raise ValueError("c must be finite and positive")
scores = defaultdict(float)
for ranking in rankings:
seen = set()
rank = 0
for doc_id in ranking:
if doc_id in seen:
continue
seen.add(doc_id)
rank += 1
scores[doc_id] += 1.0 / (c + rank)
return sorted(scores.items(), key=lambda x: (-x[1], x[0]))[:top_k]
输入 [["A", "B"], ["B", "C"]] 时,B 的分数为 1/62+1/61,高于 A 的 1/61 和 C 的 1/62。c=60 是本例参数;它影响名次贡献的差距,不应当成对所有任务都最优的常数。
设总输入条数为 N,唯一文档数为 U,累计分数耗时 O(N),完整排序耗时 O(U log U),额外空间 O(U)。top_k=0 返回空列表,空输入也返回空列表。工程中还需要限制候选规模,避免外部服务返回异常大的列表拖慢接口。
12、假设正确文档已经进了最终上下文,答案还是错的,你怎么继续定位?
首先要明确“正确文档”是否真的提供了完整证据。有标题相关的文档,不等于有适用版本、操作条件和必要步骤。如果关键条件被切掉,仍是证据问题;如果证据完整,再检查模型是否选错段落、漏掉否定条件,或把不确定表述变成了确定结论。
我会固定同一个问题、模型配置和文档版本,保存最终实际发送的上下文。用只包含必要证据的输入作对照,再逐步加入原先的其他候选,观察错误是否随干扰出现。这样能区分最小证据下仍然答错,和上下文组装导致的信息混淆;但一次对照成功不够,仍要在同类问题上重复验证。
答案验证按事实主张进行。例如“散热不良可能触发保护”不能被改写成“一定是风扇损坏”。引用链接正确也不代表支持这个主张。最后根据定位结果调整证据范围、生成约束或模型选择,再回归相关样本;如果资料冲突或条件不全,明确说明缺失信息比强行给出处理建议更合适。
本篇目录
当前问题0115
- 01
介绍一下你做的 Agent 项目,你主要负责哪些部分?
- 02
你刚才提到查手册。为什么用 RAG,而不是把手册拿去微调模型?
- 03
RAG 里的文档一般怎么切分?chunk 越大是不是越容易保留完整信息?
- 04
Embedding 是什么?余弦相似度和内积有什么区别?
- 05
Function Calling 的完整流程是什么?在你的项目里怎么用?
- 06
有了 JSON Schema,是不是就不会出现工具调用错误了?
- 07
我们问一点模型基础。Attention 怎么计算,为什么要除以 √d_k?
- 08
自回归语言模型训练时可以并行计算,为什么生成答案通常还要逐 token 进行?
- 09
你的检索会同时用关键词和向量吗?为什么需要两路,结果又怎么合并?
- 10
召回后为什么还要重排?把召回数量一直增大,会不会效果一直提高?
- 11
写一个 RRF 融合函数。输入是多路排好序的文档 ID,说明重复 ID、并列和复杂度怎么处理。
- 12
假设正确文档已经进了最终上下文,答案还是错的,你怎么继续定位?
- 13
Embedding 是什么?余弦相似度和内积有什么区别?
- 14
我们问一点模型基础。Attention 怎么计算,为什么要除以 √d_k?
- 15
召回后为什么还要重排?把召回数量一直增大,会不会效果一直提高?
