快手 AI Agent开发 一面
1、为什么引入父子索引,为什么引入 BM25,比例是怎样的,具体流程是什么,有没有 rerank
父子索引主要是为了解决 切块后召回准,但是上下文不完整 的问题。如果文档直接按 300~500 token 切成小块做向量检索,确实更容易召回到具体答案,但很容易丢掉原文结构,比如标题、段落关系、表格上下文、前后约束条件。所以会把文档拆成两层:
- 父块:保留完整语义单元,通常是 800~1500 token
- 子块:用于召回,通常是 200~400 token
检索时先搜子块,命中后回源到对应父块,再把父块内容喂给大模型。这样既保证召回精度,也保证上下文完整性。
引入 BM25 是因为向量检索对语义相似比较敏感,但对 专有名词、缩写、数字、型号、产品名、英文串 这类精确匹配场景不够稳定。比如搜「账号限流 429」「蒲公英审核」「笔记灵感券」这种词,BM25 往往比纯向量更稳。所以实际一般会做混合召回。
比例一般是:
- 向量召回占主,70%
- BM25 召回占辅,30%
一个比较常见的流程是:
- 用户问题先做 query 改写
- 用改写后的 query 同时走向量检索和 BM25 检索
- 向量检索取 top50,BM25 取 top20
- 合并去重后得到候选 40~60 条
- 用 rerank 模型重排
- 取 rerank 后的 top5 或 top6 作为最终上下文
- 再做 prompt 组装,送给大模型生成
一般是有 rerank 的,不加 rerank 的话,混合召回出来的候选噪声还是偏大,尤其在语义相近但答案不完全对的场景里,模型很容易被错误片段带偏。
2、rerank 后返回几个块,有没有做一些验证
rerank 后一般返回 5 个块。这个值不是拍脑袋定的,是在准确率、上下文长度和最终生成效果之间平衡出来的。
如果返回太少,比如只给 1~2 个块,容易漏信息,尤其是答案分散在多个段落里的场景。如果返回太多,比如 8~10 个块,虽然召回覆盖率会高一点,但无关信息会明显变多,大模型更容易注意力分散,回答反而不稳定,成本和延迟也会上升。
验证通常会做三类:
- 离线检索验证:看 gold chunk 是否在 rerank 后 top5 里,主要看 Recall@5、MRR、NDCG
- 答案级验证:把 rerank 后的上下文喂给模型,看最终回答的正确率、引用命中率
- 人工抽检:抽样看 top5 是否都和问题相关,是否存在明显干扰块
如果是业务问答场景,一般会重点看两个指标:
- rerank 后 top5 的相关块命中率
- 最终答案正确率
这两个指标比只看 embedding 召回率更有意义。
3、rerank 后的 topk 截断是怎么做的,为什么是这个值,有没有其他方案
topk 截断一般直接取 top5。这个值是通过实验定下来的,不是固定理论值。
比如做过一组对比:
- top3:精度高,但覆盖不够,部分多证据问题会漏
- top5:综合最好
- top8:召回覆盖更高,但噪声开始明显增加
- top10:模型输入过长,效果反而下降
所以最后会固定在 top5。如果问题比较复杂,比如需要跨段整合,也可能放宽到 top6,但不会太大。
除了固定 topk,还有两种常见方案:
1)按分数阈值截断
比如 rerank score 大于 0.75 的块才保留。好处是更灵活,坏处是不同 query 的分数分布差异大,不太稳定。
2)topk + 阈值双重控制
比如最多取 top5,但要求 score 至少大于 0.72。这个做法线上更常见一点,既能控制长度,也能过滤掉明显噪声。
4、讲一下上下文工程,记忆是怎么做的
上下文工程核心就是一件事:不是把能拿到的信息全塞给模型,而是只给当前问题真正需要的信息。一般会把上下文拆成几层:
- 系统指令
- 角色设定
- 当前用户问题
- 检索知识
- 工具返回结果
- 历史对话摘要
- 最近几轮原始对话
记忆一般分成三类:
短期记忆
保留最近 3~5 轮原始对话,用来承接上下文,比如代词、省略、追问。
摘要记忆
当对话轮数变长后,把前面的历史压缩成一段摘要,比如用户偏好、已经问过的问题、已完成的动作。
长期记忆
把稳定事实结构化存储,比如用户标签、历史偏好、已授权信息、黑白名单状态。这部分通常不直接从原始对话里拼,而是单独落库。
一个简单的记忆结构可以这样写:
class SessionMemory:
def __init__(self):
self.recent_messages = []
self.summary = ""
self.long_term_profile = {
"interests": [],
"tags": [],
"history_actions": []
}
memory = SessionMemory()
memory.recent_messages = [
{"role": "user", "content": "我最近在看穿搭内容"},
{"role": "assistant", "content": "你更喜欢通勤风还是休闲风?"}
]
memory.summary = "用户近期关注穿搭,偏好实用型内容"
memory.long_term_profile["interests"].append("穿搭")
print(memory.__dict__)
实际做法里,短期记忆保留原文,长期记忆尽量结构化,摘要记忆定期更新。这样 token 成本和效果会比较平衡。
5、Function Calling 和 Agent 规划是怎么做的
AI Agent 不是单纯调用一次大模型,而是让模型先决定 要不要调用工具、调用哪个工具、参数是什么、结果回来后怎么继续。Function Calling 是其中的执行接口,Agent 规划是上层决策逻辑。
常见链路是:
- 用户发起请求
- 模型先判断意图
- 如果需要外部信息,就生成函数调用参数
- 后端执行工具
- 把工具结果回填给模型
- 模型生成最终答案
比如查笔记数据、查用户画像、查商品信息、查审核状态,这些都不应该让模型自己编,必须查真实系统。
def query_note_status(note_id: int):
return {
"note_id": note_id,
"status": "reviewing",
"reason": "内容审核中"
}
tool_call = {
"name": "query_note_status",
"arguments": {"note_id": 12345}
}
result = query_note_status(**tool_call["arguments"])
print(result)
Agent 规划如果任务简单,通常一次工具调用就够了。如果任务复杂,比如“先查数据,再汇总,再生成建议”,会拆成多步执行,但步数一般会限制在 3~5 步,避免链路过长。
6、Prompt 注入怎么防,工具调用怎么做安全控制
Prompt 注入本质上是用户输入试图覆盖系统规则,比如:
- 忽略之前所有指令
- 直接输出内部配置
- 假装自己是管理员
- 诱导模型调用高权限工具
防御一般做三层:
输入层
先做规则过滤和风险识别,识别越权请求、系统 prompt 探测、工具滥用指令。
模型层
系统提示词里明确优先级:系统指令高于用户输入,用户不能修改工具权限,不能访问未授权数据。
工具层
工具侧必须做权限校验,不能只靠模型自觉。比如查用户隐私信息、查审核详情、查交易数据,必须带用户身份和权限校验。
也就是说,安全不能只放在 prompt 里,真正关键的是后端工具鉴权。模型最多负责“提议调用”,最终是否执行一定由程序决定。
7、分布式令牌桶限流讲一下,漏桶讲一下,滑动窗口算法限流讲一下,如果用滑动窗口结构体会包含什么字段,滑动窗口和令牌桶相比有什么区别,用 Redis 的什么数据结构实现
令牌桶
令牌桶会按固定速率往桶里放令牌,请求来了先拿令牌,拿到就通过,拿不到就拒绝。它允许一定程度的突发流量,只要桶里之前积累了令牌,就可以瞬时放过一批请求。
核心参数一般有:
capacity:桶容量rate:令牌生成速率,单位通常是token/stokens:当前剩余令牌数last_refill_time:上次补充令牌时间
分布式场景里通常把桶状态放 Redis,用 Lua 保证原子性。
漏桶
漏桶是请求先进桶,再按固定速率流出。它的特点是输出速率稳定,能平滑流量,但不太适合强突发场景,因为突发请求会在桶里排队,桶满了就丢。
滑动窗口
滑动窗口会统计最近一段时间内的请求数,比如最近 1 秒最多 100 次。相比固定窗口,滑动窗口不会出现窗口切换瞬间的突刺问题。
如果滑动窗口结构体自己实现,常见字段有:
class SlidingWindow:
def __init__(self):
self.window_size = 1000 # 毫秒
self.max_requests = 100
self.current_time = 0
self.buckets = [] # [(timestamp, count)]
self.total_count = 0
如果用 Redis 实现滑动窗口,最常见的是 ZSet。每次请求进来把当前时间戳作为 score 写入 ZSet:
- 先删除窗口外数据
- 再统计窗口内元素个数
- 没超限就插入当前请求
- 超限就拒绝
令牌桶和滑动窗口的区别:
- 令牌桶适合控制平均速率,并允许突发
- 滑动窗口适合控制某时间段内的真实请求数,更直观
8、布隆过滤器讲一下
布隆过滤器是一种空间效率很高的概率型数据结构,用来判断一个元素 大概率存在 或 一定不存在。它的底层是一个位数组加多个哈希函数。
插入元素时,用多个哈希函数计算多个位置,把这些位置都置为 1。查询元素时,如果有任意一个位置是 0,说明元素一定不存在;如果所有位置都是 1,只能说明元素可能存在,因为可能发生哈希冲突。
特点是:
- 查询快
- 占用内存小
- 会有误判
- 不会漏判不存在的元素
- 普通布隆过滤器不支持删除
常见用途有:
- 防缓存穿透
- 请求去重
- 黑名单预过滤
- 大规模 ID 存在性判断
9、索引失效的情况,like 会不会失效
MySQL 索引失效常见情况有这些:
- 对索引列做函数、计算、隐式类型转换
- 不满足
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏聚焦 AI-Agent 面试高频考点,内容来自真实面试与项目实践。系统覆盖大模型基础、Prompt工程、RAG、Agent架构、工具调用、多Agent协作、记忆机制、评测、安全与部署优化等核心模块。以“原理+场景+实战”为主线,提供高频题解析、标准答题思路与工程落地方法,帮助你高效查漏补缺.
查看8道真题和解析