9.6 KiB
9.6 KiB
面试知识点总结
基于本项目的技术栈,总结面试常问知识点
一、LangChain 核心知识点
1.1 LangChain Agent 机制
Q1: 什么是 LangChain Agent? 它与 Chain 的区别?
| 特性 | Chain | Agent |
|---|---|---|
| 执行方式 | 固定流程,按顺序执行 | 动态决策,自主选择下一步 |
| 工具调用 | 预定义,需手动编写 | 自动判断是否调用工具 |
| 适用场景 | 固定流程 (如 RAG) | 需要推理和决策的场景 |
项目中的应用:
# 使用 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 执行过程中注入自定义逻辑。
项目中的三种中间件:
- wrap_tool_call - 工具调用包装
@wrap_tool_call
def monitor_tool(request, handler):
# 工具调用前
logger.info(f"执行工具: {request.tool_call['name']}")
result = handler(request) # 调用实际工具
# 工具调用后
return result
- before_model - 模型调用前
@before_model
def log_before_model(state, runtime):
# 每次调用 LLM 前记录日志
logger.info(f"即将调用模型,当前有 {len(state['messages'])} 条消息")
return None
- dynamic_prompt - 动态提示词
@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 中的关键环节有哪些?
- 文档加载: PyPDFLoader, TextLoader
- 文本分片: RecursiveCharacterTextSplitter
- chunk_size: 每片大小
- chunk_overlap: 重叠区域 (保持上下文)
- separators: 分隔符优先级
- 向量化: Embedding 模型
- 向量存储: Chroma
- 相似度检索: Cosine similarity, Top-K
- 结果拼接: Query + Context → Prompt
Q7: 如何优化 RAG 效果?
| 优化方向 | 方法 |
|---|---|
| 检索质量 | 调整 chunk_size、overlap、使用 better embedding |
| 召回率 | 增加 Top-K、使用混合检索 |
| 排序rerank | 使用 rerank 模型二次排序 |
| Prompt 工程 | 优化 RAG Prompt 模板 |
2.3 项目中的 RAG 实现
# 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 项目中的应用
# 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: 如何实现流式输出?
# 方法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)
# 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 日志管理
# 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 路径管理
# 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 去重
# 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 工具调用的安全性?
- 工具入参校验: Pydantic 类型提示
- 调用次数限制: 最多 N 次
- 工具白名单: 只允许调用指定工具
- 中间件监控: 记录所有调用
Q13: Agent 如何实现"思考过程"可视化?
# 项目中的实现
for chunk in res:
last_msg = chunk["messages"][-1]
if last_msg.tool_calls:
# 显示思考过程
print(f"工具调用: {last_msg.tool_calls}")
Q14: RAG 召回率低怎么排查?
- 检查 chunk_size 是否合适
- 检查 embedding 质量
- 查看检索到的文档是否相关
- 调整 Top-K 值
- 检查分片是否有意义 (避免切断句子)
Q15: 生产环境部署注意什么?
- API 限流: 添加重试机制
- 错误处理: 工具调用失败处理
- 日志: 完整日志记录
- 监控: 调用次数、耗时监控
- 缓存: 热门 query 缓存结果