388 lines
9.6 KiB
Markdown
388 lines
9.6 KiB
Markdown
# 面试知识点总结
|
||
|
||
> 基于本项目的技术栈,总结面试常问知识点
|
||
|
||
---
|
||
|
||
## 一、LangChain 核心知识点
|
||
|
||
### 1.1 LangChain Agent 机制
|
||
|
||
#### Q1: 什么是 LangChain Agent? 它与 Chain 的区别?
|
||
|
||
| 特性 | Chain | Agent |
|
||
|------|-------|-------|
|
||
| 执行方式 | 固定流程,按顺序执行 | 动态决策,自主选择下一步 |
|
||
| 工具调用 | 预定义,需手动编写 | 自动判断是否调用工具 |
|
||
| 适用场景 | 固定流程 (如 RAG) | 需要推理和决策的场景 |
|
||
|
||
**项目中的应用**:
|
||
```python
|
||
# 使用 create_agent 创建 Agent
|
||
agent = create_agent(
|
||
model=ChatTongyi(model="qwen3-max"),
|
||
tools=[rag_summarize, get_weather, get_user_id],
|
||
system_prompt="你是一个智能助手"
|
||
)
|
||
```
|
||
|
||
#### Q2: 解释 ReAct 框架在 Agent 中的应用
|
||
|
||
**ReAct = Reasoning + Acting**
|
||
|
||
```
|
||
Thought: 我需要知道用户所在城市的天气
|
||
Action: get_weather
|
||
Action Input: 深圳
|
||
Observation: 天气晴朗,26°C
|
||
Thought: 现在我有天气信息,可以回答用户问题了
|
||
```
|
||
|
||
**项目中的体现**:
|
||
- 系统提示词明确要求遵循 ReAct 框架
|
||
- Agent 自动完成 "思考→行动→观察→再思考" 循环
|
||
- 最多支持 5 次工具调用
|
||
|
||
### 1.2 LangChain Middleware 中间件机制
|
||
|
||
#### Q3: LangChain 中间件是什么? 如何实现?
|
||
|
||
LangChain 中间件是 LangGraph 提供的钩子机制,用于在 Agent 执行过程中注入自定义逻辑。
|
||
|
||
**项目中的三种中间件**:
|
||
|
||
1. **wrap_tool_call** - 工具调用包装
|
||
```python
|
||
@wrap_tool_call
|
||
def monitor_tool(request, handler):
|
||
# 工具调用前
|
||
logger.info(f"执行工具: {request.tool_call['name']}")
|
||
result = handler(request) # 调用实际工具
|
||
# 工具调用后
|
||
return result
|
||
```
|
||
|
||
2. **before_model** - 模型调用前
|
||
```python
|
||
@before_model
|
||
def log_before_model(state, runtime):
|
||
# 每次调用 LLM 前记录日志
|
||
logger.info(f"即将调用模型,当前有 {len(state['messages'])} 条消息")
|
||
return None
|
||
```
|
||
|
||
3. **dynamic_prompt** - 动态提示词
|
||
```python
|
||
@dynamic_prompt
|
||
def report_prompt_switch(request):
|
||
is_report = request.runtime.context.get("report", False)
|
||
if is_report:
|
||
return load_report_prompts() # 切换为报告生成 Prompt
|
||
return load_system_prompts()
|
||
```
|
||
|
||
#### Q4: 中间件的应用场景有哪些?
|
||
|
||
| 场景 | 中间件类型 |
|
||
|------|-----------|
|
||
| 日志记录 | before_model, wrap_tool_call |
|
||
| 性能监控 | wrap_tool_call |
|
||
| 动态提示词 | dynamic_prompt |
|
||
| 权限控制 | wrap_tool_call |
|
||
| 结果缓存 | wrap_tool_call |
|
||
|
||
---
|
||
|
||
## 二、RAG (检索增强生成)
|
||
|
||
### 2.1 RAG 核心流程
|
||
|
||
```
|
||
用户 query
|
||
│
|
||
▼
|
||
┌─────────────────┐
|
||
│ 向量检索 │ ← Chroma Retriever
|
||
└────────┬────────┘
|
||
│ 返回 Top-K 相关文档
|
||
▼
|
||
┌─────────────────┐
|
||
│ 构建 Prompt │ ← 拼接 query + context
|
||
└────────┬────────┘
|
||
│
|
||
▼
|
||
┌─────────────────┐
|
||
│ LLM 生成回答 │ ← 通义千问
|
||
└────────┬────────┘
|
||
│
|
||
▼
|
||
返回结果
|
||
```
|
||
|
||
### 2.2 关键面试题
|
||
|
||
#### Q5: 为什么需要 RAG? 直接用 LLM 不可以吗?
|
||
|
||
| 方案 | 优点 | 缺点 |
|
||
|------|------|------|
|
||
| 直接 LLM | 简单 | 知识截止、可能幻觉、无法访问私域知识 |
|
||
| RAG | 实时性、可引用私域知识、减少幻觉 | 增加复杂度、依赖检索质量 |
|
||
|
||
#### Q6: RAG 中的关键环节有哪些?
|
||
|
||
1. **文档加载**: PyPDFLoader, TextLoader
|
||
2. **文本分片**: RecursiveCharacterTextSplitter
|
||
- chunk_size: 每片大小
|
||
- chunk_overlap: 重叠区域 (保持上下文)
|
||
- separators: 分隔符优先级
|
||
3. **向量化**: Embedding 模型
|
||
4. **向量存储**: Chroma
|
||
5. **相似度检索**: Cosine similarity, Top-K
|
||
6. **结果拼接**: Query + Context → Prompt
|
||
|
||
#### Q7: 如何优化 RAG 效果?
|
||
|
||
| 优化方向 | 方法 |
|
||
|----------|------|
|
||
| 检索质量 | 调整 chunk_size、overlap、使用 better embedding |
|
||
| 召回率 | 增加 Top-K、使用混合检索 |
|
||
| 排序rerank | 使用 rerank 模型二次排序 |
|
||
| Prompt 工程 | 优化 RAG Prompt 模板 |
|
||
|
||
### 2.3 项目中的 RAG 实现
|
||
|
||
```python
|
||
# rag_service.py
|
||
class RagSummarizeService:
|
||
def rag_summarize(self, query: str) -> str:
|
||
# 1. 向量检索
|
||
context_docs = self.retriever.invoke(query)
|
||
|
||
# 2. 拼接上下文
|
||
context = ""
|
||
for doc in context_docs:
|
||
context += f"[参考资料]: {doc.page_content} | {doc.metadata}\n"
|
||
|
||
# 3. 调用 LLM 生成
|
||
return self.chain.invoke({
|
||
"input": query,
|
||
"context": context
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
## 三、向量数据库 (Chroma)
|
||
|
||
### 3.1 Chroma 核心概念
|
||
|
||
| 概念 | 说明 |
|
||
|------|------|
|
||
| Collection | 类似表,存储一类文档 |
|
||
| Embedding Function | 向量化函数 |
|
||
| Document | 包含 page_content 和 metadata |
|
||
| Retriever | 检索器,支持相似度搜索 |
|
||
|
||
### 3.2 面试题
|
||
|
||
#### Q8: Chroma 的优势?
|
||
|
||
- 轻量级,易于部署
|
||
- Python 原生,集成方便
|
||
- 支持持久化存储
|
||
- 与 LangChain 无缝集成
|
||
|
||
#### Q9: Chroma 与其他向量库 (Milvus/Pinecone) 的区别?
|
||
|
||
| 特性 | Chroma | Milvus | Pinecone |
|
||
|------|--------|--------|----------|
|
||
| 部署方式 | 本地/嵌入 | 服务端 | SaaS |
|
||
| 规模 | 中小 | 大规模 | 大规模 |
|
||
| 成熟度 | 一般 | 高 | 高 |
|
||
|
||
---
|
||
|
||
## 四、Streamlit Web 开发
|
||
|
||
### 4.1 项目中的应用
|
||
|
||
```python
|
||
# app.py
|
||
import streamlit as st
|
||
|
||
st.title("智扫通智能机器人客服")
|
||
|
||
# 会话状态管理
|
||
if "agent" not in st.session_state:
|
||
st.session_state["agent"] = ReactAgent()
|
||
|
||
# 聊天记录
|
||
for message in st.session_state["message"]:
|
||
st.chat_message(message["role"]).write(message["content"])
|
||
|
||
# 用户输入
|
||
prompt = st.chat_input()
|
||
|
||
# 流式输出
|
||
if prompt:
|
||
stream = agent.excute_stream(prompt)
|
||
st.chat_message("assistant").write_stream(capture(stream))
|
||
```
|
||
|
||
### 4.2 关键面试题
|
||
|
||
#### Q10: Streamlit 的核心机制?
|
||
|
||
- **声明式 UI**: 通过 st.xxx() 声明组件
|
||
- **脚本式**: 每次交互重新运行整个脚本
|
||
- **状态管理**: session_state 保持会话状态
|
||
- **热重载**: 修改代码自动刷新
|
||
|
||
#### Q11: 如何实现流式输出?
|
||
|
||
```python
|
||
# 方法1: write_stream
|
||
st.write_stream(generator_function)
|
||
|
||
# 方法2: 自定义 (本项目使用)
|
||
def capture(generator, cache_list):
|
||
for chunk in generator:
|
||
cache_list.append(chunk)
|
||
for char in chunk:
|
||
time.sleep(0.05) # 模拟打字效果
|
||
yield char
|
||
```
|
||
|
||
---
|
||
|
||
## 五、设计模式
|
||
|
||
### 5.1 工厂模式 (Factory Pattern)
|
||
|
||
```python
|
||
# model/factory.py
|
||
class BaseModelFactory(ABC):
|
||
@abstractmethod
|
||
def generator(self) -> Optional[Embeddings | BaseChatModel]:
|
||
pass
|
||
|
||
class ChatModelFactory(BaseModelFactory):
|
||
def generator(self) -> Optional[BaseChatModel]:
|
||
return ChatTongyi(model=rag_conf["chat_model_name"])
|
||
|
||
class EmbeddingsFactory(BaseModelFactory):
|
||
def generator(self) -> Optional[Embeddings]:
|
||
return DashScopeEmbeddings(model=rag_conf["embeddings_model_name"])
|
||
```
|
||
|
||
**好处**: 统一创建入口,便于后续更换模型
|
||
|
||
### 5.2 配置中心化
|
||
|
||
所有配置集中在 config/ 目录的 YAML 文件中:
|
||
- rag.yaml: 模型配置
|
||
- chroma.yaml: 向量库配置
|
||
- agent.yaml: Agent 配置
|
||
- prompts.yaml: Prompt 路径配置
|
||
|
||
### 5.3 中间件模式
|
||
|
||
利用装饰器和钩子实现横切关注点:
|
||
- 日志
|
||
- 监控
|
||
- 动态提示词切换
|
||
|
||
---
|
||
|
||
## 六、工程实践
|
||
|
||
### 6.1 日志管理
|
||
|
||
```python
|
||
# utils/logger_handler.py
|
||
def get_logger(name: str = "Agent"):
|
||
logger = logging.getLogger(name)
|
||
# 双写: 控制台 + 文件
|
||
console_handler = logging.StreamHandler()
|
||
file_handler = logging.FileHandler(log_file, encoding='utf-8')
|
||
return logger
|
||
```
|
||
|
||
### 6.2 路径管理
|
||
|
||
```python
|
||
# utils/path_tool.py
|
||
def get_project_root():
|
||
cur_file = os.path.abspath(__file__)
|
||
proj_dir = os.path.dirname(os.path.dirname(cur_file))
|
||
return proj_dir
|
||
|
||
def get_abs_path(relative_path):
|
||
return os.path.join(get_project_root(), relative_path)
|
||
```
|
||
|
||
### 6.3 MD5 去重
|
||
|
||
```python
|
||
# vector_store.py
|
||
# 通过 MD5 避免重复加载文档
|
||
md5_hex = get_file_md5_hex(path)
|
||
if check_md5_hex(md5_hex):
|
||
continue # 已存在,跳过
|
||
self.vector_store.add_documents(split_doc)
|
||
save_md5(md5_hex)
|
||
```
|
||
|
||
---
|
||
|
||
## 七、LLM & Embedding
|
||
|
||
### 7.1 通义千问 (Qwen)
|
||
|
||
- **模型**: qwen3-max (阿里云)
|
||
- **调用**: langchain_community.chat_models.tongyi.ChatTongyi
|
||
|
||
### 7.2 DashScope Embedding
|
||
|
||
- **模型**: text-embedding-v4
|
||
- **用途**: 将文本转为向量
|
||
- **调用**: langchain_community.embeddings.DashScopeEmbeddings
|
||
|
||
---
|
||
|
||
## 八、常见面试扩展问题
|
||
|
||
### Q12: 如何保证 Agent 工具调用的安全性?
|
||
|
||
1. **工具入参校验**: Pydantic 类型提示
|
||
2. **调用次数限制**: 最多 N 次
|
||
3. **工具白名单**: 只允许调用指定工具
|
||
4. **中间件监控**: 记录所有调用
|
||
|
||
### Q13: Agent 如何实现"思考过程"可视化?
|
||
|
||
```python
|
||
# 项目中的实现
|
||
for chunk in res:
|
||
last_msg = chunk["messages"][-1]
|
||
if last_msg.tool_calls:
|
||
# 显示思考过程
|
||
print(f"工具调用: {last_msg.tool_calls}")
|
||
```
|
||
|
||
### Q14: RAG 召回率低怎么排查?
|
||
|
||
1. 检查 chunk_size 是否合适
|
||
2. 检查 embedding 质量
|
||
3. 查看检索到的文档是否相关
|
||
4. 调整 Top-K 值
|
||
5. 检查分片是否有意义 (避免切断句子)
|
||
|
||
### Q15: 生产环境部署注意什么?
|
||
|
||
1. **API 限流**: 添加重试机制
|
||
2. **错误处理**: 工具调用失败处理
|
||
3. **日志**: 完整日志记录
|
||
4. **监控**: 调用次数、耗时监控
|
||
5. **缓存**: 热门 query 缓存结果
|