
自我介绍
## 1.rag项目背景
*RAG（Retrieval-Augmented Generation，检索增强生成）是一种把“信息检索”和“文本生成”结合起来的 AI 方法：当用户提问时，系统会先从外部知识库（如文档、数据库或私有数据）中检索出相关内容，再将这些内容连同问题一起交给大模型生成答案。这样做可以避免模型凭空编造信息，让回答更加准确、可控，并且能够利用最新或专属的数据。*

## 2.混合检索怎么做的
*混合检索（Hybrid Retrieval）是将关键词检索（如基于 BM25 的搜索）和向量语义检索（如基于 FAISS 或 Milvus）结合起来的一种方法：系统会同时用“字面匹配”和“语义相似”两种方式查找相关内容，然后将结果合并并通过加权或重排序（rerank）筛选出最相关的部分，最后提供给大模型生成答案。这样既保证了检索的精确性，又提升了对自然语言语义的理解能力，从而显著提高整体检索和问答效果。*

## 3.直接用用户问题检索吗
*不会直接用用户原问题去检索。实际工程里一般会先做一层简单的 Query 处理，比如改写或补全，把口语问题变成更适合检索的表达，尤其是多轮对话场景会把上下文合进去。这样可以提升召回的稳定性和相关性。*
*简单场景可以直接用原问题，但线上系统基本都会做这一步。*

## 4.有没有做过相关性能评估，召回率是多少
*第一，指标怎么定义。*  
*一般不会只说“召回率”，而是用 **Recall@K / Context Recall** 这类指标，比如 Top5、Top10 命中率。*
*本质就是：相关文档有没有被检索出来。*
*第二，实际效果区间。*  
*在我做过的项目里（偏企业知识库场景）：*
- 向量检索 baseline：Recall@5 大概 **60%~70%**
- 加 Query Rewrite + 混合检索：能到 **75%~85%**
- 再加 rerank：Top3 命中率能稳定在 **80%+**
这个数不是固定的，主要取决于三点：
- 数据质量（chunk 切分、embedding）
- query 处理（有没有改写/扩展）
- 是否做 rerank

## 5.长期记忆和短期记忆是怎么做的
*短期记忆主要是对当前会话的上下文管理，一般用 sliding window 保留最近几轮对话，超出长度后会做摘要压缩，保证关键信息还在，同时控制 token 成本。*
*长期记忆是把用户的关键信息做结构化沉淀，比如偏好、历史行为、重要事实等，存到数据库或者向量库里。后续对话时通过检索把相关内容再注入到上下文里，本质上是一个轻量的 RAG。*
*实际工程里两者会结合用：短期保证对话连贯，长期保证跨会话的个性化和记忆能力。*

## 6.长期记忆是怎么存储的
*长期记忆一般不会只用一种存储，而是分层做。*
*一类是结构化信息，比如用户偏好、标签、配置，这种直接存关系型数据库或 KV，读写稳定、查询也快。*
*一类是非结构化记忆，比如对话中抽取出来的事实、经历，会做 embedding 存到向量库里，方便后续按语义检索。*
*有些场景还会做一层“记忆抽取”，不是把原始对话全存，而是用模型把关键信息提炼成短文本再入库，避免噪音和冗余。*
*整体就是：结构化走数据库，语义记忆走向量库，查询时按需检索再注入上下文。*

## 7.意图识别是怎么做的，直接用prompt吗
*不会只靠 prompt。*
*简单场景可以用 prompt 做分类，比如让模型输出固定标签，这种上线快，但稳定性一般。*
*工程上更常见是两种方式结合：*
*一是轻量模型或规则做第一层意图分类（比如分类模型或关键词规则），保证稳定和可控；*  
*二是大模型做补充或兜底，处理复杂、模糊意图。*
*如果对一致性要求高，还会做 schema 约束（固定输出格式），甚至把意图拆成多维标签（任务类型、领域、是否需要检索等）。*
*整体思路是：**能规则/小模型解决的先挡住，大模型负责复杂情况，而不是完全依赖 prompt。***

## 8.这里面用的mcp有哪些
*如果说在这个链路里用到的 MCP，我理解主要是做**能力编排和外部调用的标准化**，不是核心算法部分。*
*比较常见的几个点：*
- ***检索侧 MCP**：把向量检索、关键词检索封装成统一接口，模型通过 MCP 去调用，而不是写死逻辑*
- ***记忆 MCP**：长期记忆的读写，比如用户信息查询、记忆写入，做成工具接口给模型调用*
- ***意图/工具路由 MCP**：模型根据意图决定调哪个能力，比如查知识库、查用户信息、调用业务接口*
- ***外部系统 MCP**：比如查询订单、触发业务操作，这些都通过 MCP 做标准化接入*
*实际好处是两点：*
*一是**解耦**，模型和后端能力分开演进；*  
*二是**可控性**，哪些能力能调用、怎么调用，都可以在 MCP 层做约束。*
*整体上，MCP更像是“工具层/能力层”的统一协议，让大模型在 RAG、记忆、意图这些环节里可以稳定地调用外部系统。*

## 9.有了解skills吗，和mcp的区别
*skills是渐进性披露 MCP更像是一整套工具调用级 他们最显著的区别是token消耗上,skills更像是一本书的目录,当需要使用这个功能时,大模型才去学习了解掌握,所以现在有个很大的趋势是用skills+cli工具逐步替代臃肿的不必要的MCP工具*
- 用 Skills 控制上下文规模
- 用 CLI/外部执行保证能力落地
- 避免把大量工具 schema 一次性塞进 prompt
*但MCP 也不会完全被替代，在**多团队协作、复杂系统集成、强治理场景**下，它的标准化优势还是很有价值。*

## 10.使用claude code、cursor这类ai编程工具，你有哪些使用心得
这个问题挺实际的，我日常用 Cursor 比较多，Claude Code 也在用，说几个真实感受：
### 用得顺手的场景
- **样板代码生成**：CRUD、proto 定义、单测骨架，直接让它生成，省去大量机械劳动
- **跨文件重构**：Cursor 的 `@codebase` 索引能理解上下文，改个接口签名然后让它同步所有调用方，效率很高
- **不熟悉的技术栈**：比如临时写个 Python 脚本或者前端页面，AI 补全基本够用
### 踩过的坑
- **过度信任生成结果**：早期会直接 accept，后来发现它会"幻觉式地"引用不存在的方法或者用错 API 版本，现在习惯 diff 必看
- **上下文窗口限制**：项目大了之后它对全局架构的理解会飘，给出的方案有时和现有设计冲突，需要手动喂上下文
- **让它做设计决策是浪费**：拿 AI 讨论"要不要用 MQ"这种问题，答案基本是废话，这类问题还是得自己想
### 我现在的用法
把它当**高级自动补全 + 初稿生成器**，而不是"架构师"。
核心流程：自己想清楚结构 → 让 AI 填肉 → 人工 review 逻辑正确性。

## 11.claude code泄漏的源码有了解吗，具体有哪些好的做法
了解这个事件，3月31日 npm 包里意外打包了 `.map` 文件，51万行 TypeScript 直接曝光。说说从中能学到的 agent 工程化做法：
### 几个值得借鉴的设计
**1. 三层记忆架构（解决 context entropy）**
- `MEMORY.md` 作为轻量索引，每行约 150 字符的"指针"，**永久加载**到 context
- 不是 store-everything 的 RAG，而是精炼后的结构化事实
- agent 被明确指令：把自己的记忆当 **hint 而非事实**，执行前要 verify
**2. autoDream — 空闲时的记忆整合**
后台 subagent 在用户 idle 时运行，做 4 件事：
```
Orient → Gather → Consolidate → Prune
```
把零散观察合并、消除矛盾、把模糊 insight 固化为 fact。这个思路对长期 agent 很实用，我们自己做 RAG 时其实也该有类似的"写回"机制。
**3. KAIROS — 主动式 daemon 模式**
从 reactive 转向 always-on，后台监听日志、主动触发任务。这是 agent 从"工具"进化为"协作者"的关键设计。
**4. ULTRAPLAN — 任务卸载**
复杂规划任务 offload 给远程 Opus 会话，最长跑 30 分钟。本质是**分级调度**——轻任务本地快模型，重任务找强模型。

---
对我启发最大的是**记忆设计**这块——当前大多数 agent 框架的记忆要么是 full context 堆砌，要么是向量召回，Anthropic 这个"指针索引 + 定期整合"的方式在工程上更可控。
另外泄露还暴露了一个有意思的数字：内部模型 Capybara v8 的 false claims rate 是 29-30%，反而比 v4 的 16.7% 更差——说明 agentic 场景下 scaling 不是银弹，工程侧的约束同样关键。

## 12.本地部署Ollama模型，并发调用是一个接一个的响应还是并发的

### Ollama 默认行为：串行
默认情况下同一个模型**排队处理**，并发请求会 queue 住，一个跑完再跑下一个。
原因是模型加载后占满显存，单卡跑多个并发推理会 OOM 或效率极差。
### 但可以并发，关键参数：
```bash
# 控制同时加载的模型实例数（默认1）
OLLAMA_NUM_PARALLEL=4

# 控制同时保持加载的模型数量
OLLAMA_MAX_LOADED_MODELS=2
```
设了 `NUM_PARALLEL` 之后，同一模型可以真正并发推理，但代价是**每个请求的速度会下降**，显存压力翻倍。
### 实际踩坑
- 消费级显卡（比如 4090 24G）跑 `llama3:70b-q4` 本来就压线，开并发直接崩
- 小模型（7B/8B）+ 量化的情况下，`NUM_PARALLEL=2~4` 比较可行
- CPU offload 模式下并发更差，延迟会乘倍
### 生产场景怎么做
如果真要高并发，Ollama 不是最优选：

|方案|适合场景|
|---|---|
|vLLM|高并发首选，PagedAttention 真并发|
|llama.cpp server|轻量，支持 `--parallel`|
|Ollama|本地开发、单用户场景够用|

vLLM 的 continuous batching 才是真正解决并发的方案，Ollama 更多是开发体验优先的工具。


## 13.Mysql的事务级别
四个级别，从低到高：
### 隔离级别对比

|级别|脏读|不可重复读|幻读|
|---|---|---|---|
|READ UNCOMMITTED|✅ 有|✅ 有|✅ 有|
|READ COMMITTED|❌ 无|✅ 有|✅ 有|
|REPEATABLE READ|❌ 无|❌ 无|⚠️ 部分|
|SERIALIZABLE|❌ 无|❌ 无|❌ 无|

MySQL 默认是 **REPEATABLE READ**。
### 几个细节
**RR 下幻读问题**：InnoDB 用 **Gap Lock + Next-Key Lock** 解决了大部分幻读，但不是完全没有——

```sql
-- 事务A
SELECT * FROM t WHERE id > 10;  -- 读到3条

-- 事务B insert + commit

-- 事务A
UPDATE t SET val=1 WHERE id > 10;  -- 影响了4行
SELECT * FROM t WHERE id > 10;     -- 现在能看到4条了，幻读出现
```
当前读（`SELECT FOR UPDATE`）会加 Next-Key Lock，快照读（普通 `SELECT`）走 MVCC 不加锁。
**RC 在互联网场景更常用**：很多公司把隔离级别调成 RC，原因是：
- Gap Lock 少，死锁概率低
- 并发性能更好
- 业务上可以接受不可重复读
**MVCC 是 RR 的底层实现**：每个事务有自己的 Read View，通过 undo log 版本链实现快照读，不加锁也能隔离。

## 14.怎么防止幻读
### 两种手段
**1. MVCC（快照读）**
普通 `SELECT` 走版本链，事务开始时生成 Read View，后续其他事务插入的数据对你不可见。
不加锁，性能好，但只能解决**快照读**场景的幻读。
**2. 加锁（当前读）**
```sql
-- 间隙锁，锁住 (10, 20) 区间，其他事务无法在此范围插入
SELECT * FROM t WHERE id > 10 FOR UPDATE;

-- 插入意向锁会被 Gap Lock 阻塞
INSERT INTO t (id) VALUES (15);  -- 被阻塞
```
InnoDB 在 RR 下自动加 **Next-Key Lock**（Record Lock + Gap Lock），锁住记录本身和前面的间隙。
### 两种方案的边界

|场景|方案|说明|
|---|---|---|
|只读查询，允许历史快照|MVCC|无锁，性能好|
|读后要写，强一致|`SELECT FOR UPDATE`|加 Next-Key Lock|
|最严格|SERIALIZABLE|所有读自动加锁|

---

### 踩坑点
```sql
-- 先快照读（MVCC，不加锁）
SELECT * FROM t WHERE id > 10;

-- 再当前读（触发加锁）
UPDATE t SET val=1 WHERE id > 10;

-- 此时能读到其他事务新插入的行 —— 幻读！
```
**混用快照读和当前读是幻读的常见来源**，业务上如果要防幻读，从一开始就用 `FOR UPDATE` 保持一致。

## 15.Mysql的索引结构介绍下
### InnoDB 用 B+ 树
核心结构特点：
- 非叶子节点只存 **key**，不存数据
- 叶子节点存完整数据，且**双向链表**串联
- 树高一般 3~4 层，千万级数据 3 次 IO 能查到
```
          [30]
        /      \
    [10,20]   [40,50]
   /  |  \    /  |  \
[数据][数据][数据][数据][数据]  ← 叶子节点双向链表
```
---
### 聚簇索引 vs 非聚簇索引
**聚簇索引**（主键索引）：叶子节点存的是**整行数据**，一张表只有一个。
**二级索引**：叶子节点存的是**主键值**，查完还要回表。
```sql
-- 走二级索引，需要回表
SELECT * FROM t WHERE name = 'xx';

-- 覆盖索引，不回表
SELECT id, name FROM t WHERE name = 'xx';
```
---
### 为什么不用其他结构

|结构|问题|
|---|---|
|二叉树/红黑树|树太高，IO 次数多|
|Hash|不支持范围查询|
|B 树|非叶子节点存数据，扇出小，树更高；范围查询要回溯|

B+ 树叶子链表天然支持范围扫描，这是核心优势。
### 几个实际问题
**索引失效常见场景：**
- 最左前缀没满足
- 对索引列做函数/运算：`WHERE YEAR(create_time) = 2024`
- 隐式类型转换：字段是 varchar，查询传 int
**索引过多的代价：**写操作要维护所有 B+ 树，insert/update 变慢，选择性低的字段建索引意义不大。

## 16.跳表有了解吗
了解，Redis 的 ZSet 底层就用了跳表。
### 结构
多层链表叠加，每层是下层的"快速通道"：
```
L3: 1 -------------------------> 50
L2: 1 -----------> 20 --------> 50
L1: 1 ----> 10 --> 20 --> 30 --> 50
L0: 1 -> 5 -> 10 -> 15 -> 20 -> 25 -> 30 -> 50  (原始链表)
```
查找时从最高层开始，逐层下降，时间复杂度 **O(log n)**。

---
### 核心操作
**插入时随机决定层高**，每层晋升概率 50%（Redis 用 25%），期望层高 O(log n)。
不需要像 B 树那样做复杂的旋转平衡，**实现简单**是跳表的重要优势。

---
### 对比 B+ 树

| |跳表|B+ 树|
|---|---|---|
|实现复杂度|低|高|
|范围查询|链表顺序遍历，很快|叶子链表，也快|
|内存占用|指针多，稍高|紧凑|
|磁盘友好|❌|✅ 页对齐|

**跳表更适合内存场景**，B+ 树更适合磁盘 IO 场景，这是 Redis 选跳表、MySQL 选 B+ 树的核心原因。

### Redis ZSet 为什么用跳表不用 B+ 树
除了内存友好，还有一点：**ZRANGE 这类范围操作**在跳表上实现非常直观，代码简单；另外跳表支持 O(log n) 的 rank 查询，实现排行榜很自然。
有意思的是 ZSet 小数据量时还会用 **ziplist/listpack** 而不是跳表，超过阈值才转换，典型的空间换时间权衡。

## 17.为什么mysql不用跳表
### 本质是磁盘 IO 模型不同
跳表是**指针链式结构**，节点在内存中随机分布，每次指针跳转都可能是一次随机内存访问。
放到磁盘上就是**随机 IO**，每次节点访问都可能触发一次磁盘寻道，代价极高。

### B+ 树专门为磁盘设计
- 节点 = 页（InnoDB 默认 16KB），一次 IO 读一页，捞出大量 key
- 非叶子节点只存 key，**扇出极大**，3~4 层覆盖千万数据
- 叶子节点页内连续，范围扫描是**顺序 IO**
```
B+ 树一次 IO：读 16KB，包含几百个 key
跳表一次跳转：读一个节点，可能就几十字节，然后随机跳下一个地址
```

### 量化对比
千万数据：
- B+ 树：树高 3~4，最多 **3~4 次 IO**
- 跳表：O(log n) ≈ 23 跳，每跳随机 IO，**不可接受**

### 一句话总结
>跳表的随机指针在内存里没问题，到磁盘上随机 IO 会把性能打死。B+ 树是为页式存储专门设计的结构。
这也是为什么**内存型数据库**（Redis、MemTable）偏爱跳表，**磁盘型数据库**清一色 B/B+ 树。

手撕没做出来，非leetcode原题
