第11课:LangChain核心概念与RAG重构
⚠️ 版本兼容说明 :本课代码基于
langchain 1.x。该版本将schema、text_splitter、chains、memory等旧 API 迁移至langchain_classic兼容包。下方代码示例中的from langchain.xxx已同步更新为from langchain_classic.xxx。新项目推荐使用langchain_core+langgraph重写,langchain_classic仅用于学习与过渡。依赖 :
pip install langchain langchain-openai langchain-community langchain-classic chromadb
学习目标
- 理解LangChain七大核心抽象及其在设备维修系统中的对应关系
- 掌握用LangChain组件重构自研RAG的完整流程
- 了解LangChain与LlamaIndex的定位差异
1. 为什么需要LangChain?
在第一阶段(01-05课),我们手工搭建了一个RAG系统:自己写文档加载、文本分块、向量化、检索、生成。这帮助理解了原理,但在生产环境中,我们需要更成熟的框架来处理:
- 多种文档格式的适配
- 分块策略的灵活切换
- 向量数据库的统一接口
- 对话链的编排与记忆管理
LangChain正是为解决这些问题而生的。
2. 七大核心抽象与自研系统的映射
| LangChain抽象 | 自研RAG对应 | 设备维修场景说明 |
|---|---|---|
| DocumentLoader | load_documents() |
加载维修手册PDF、故障记录CSV |
| TextSplitter | split_text() |
将维修手册按章节、段落切分 |
| Embeddings | get_embedding() |
将故障描述转为向量 |
| VectorStore | ChromaDBClient |
存储和检索设备知识向量 |
| Retriever | retrieve() |
根据故障查询找到相关知识 |
| Chain | generate_answer() |
将检索结果与问题组合调用LLM |
| Memory | 无(第一阶段未实现) | 记录多轮对话上下文 |
3. 核心组件详解
3.1 DocumentLoader -- 文档加载器
python
from langchain_community.document_loaders import TextLoader, PyPDFLoader, CSVLoader
# 加载设备维修手册
manual_loader = TextLoader("knowledge/maintenance_manual.txt", encoding="utf-8")
manual_docs = manual_loader.load()
# 加载故障记录CSV
fault_loader = CSVLoader("knowledge/fault_records.csv", encoding="utf-8")
fault_docs = fault_loader.load()
print(f"加载了 {len(manual_docs) + len(fault_docs)} 个文档片段")
3.2 TextSplitter -- 文本分割器
python
from langchain_classic.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每块最大500字符
chunk_overlap=50, # 块之间重叠50字符,保证上下文连续
separators=["\n\n", "\n", "。", ";", " "]
)
chunks = splitter.split_documents(manual_docs)
print(f"分割为 {len(chunks)} 个块")
RecursiveCharacterTextSplitter的优势 :它按分隔符优先级递归分割,先尝试\n\n(段落),再尝试\n(行),再尝试句号等。这比固定长度分割更能保持语义完整。
3.3 Embeddings + VectorStore
python
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
embeddings = OpenAIEmbeddings(
model="text-embedding-3-small",
openai_api_key=os.getenv("OPENAI_API_KEY"),
openai_api_base=os.getenv("OPENAI_BASE_URL")
)
# 从文档直接创建向量库
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_maintenance"
)
注意:LangChain的Chroma.from_documents()一行代码就完成了我们自研系统中几十行的向量化和存储逻辑。
3.4 Retriever + Chain + Memory
python
from langchain_classic.chains import ConversationalRetrievalChain
from langchain_openai import ChatOpenAI
from langchain_classic.memory import ConversationBufferMemory
llm = ChatOpenAI(
model="deepseek-chat",
openai_api_key=os.getenv("OPENAI_API_KEY"),
openai_api_base=os.getenv("OPENAI_BASE_URL"),
temperature=0
)
# 带记忆的检索问答链
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
qa_chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
memory=memory
)
result = qa_chain({"question": "液压泵过热如何处理?"})
print(result["answer"])
# 追问 - Memory会自动带入历史上下文
result2 = qa_chain({"question": "需要更换什么备件?"})
print(result2["answer"])
关键理解 :ConversationalRetrievalChain内部自动完成:
- 将新问题和历史对话组合,生成一个独立查询
- 用该查询检索相关文档
- 将检索结果和问题一起发给LLM生成回答
- 将本轮对话存入Memory
4. LangChain vs LlamaIndex
| 对比维度 | LangChain | LlamaIndex |
|---|---|---|
| 定位 | 通用LLM应用框架 | 数据连接与检索专精 |
| Agent能力 | 成熟,生态丰富 | 支持但侧重数据Agent |
| RAG能力 | 灵活但需手动编排 | 开箱即用,高级索引 |
| 学习曲线 | 概念多,较陡 | 聚焦RAG,较平缓 |
| 生态 | 700+集成组件 | 160+数据连接器 |
| 适用场景 | 复杂多Agent系统 | 知识库问答为主 |
建议:设备维修系统同时需要RAG和Agent能力(知识问答+工单创建+库存查询),选择LangChain更合适。如果你的场景纯粹是知识库问答,LlamaIndex上手更快。
5. Memory类型选择
| Memory类型 | 特点 | 适用场景 |
|---|---|---|
| ConversationBufferMemory | 存储完整对话 | 短对话,调试 |
| ConversationBufferWindowMemory | 只保留最近K轮 | 中等长度对话 |
| ConversationSummaryMemory | LLM压缩历史对话 | 长对话,节省token |
| ConversationTokenBufferMemory | 按token数截断 | 需要精确控制成本 |
设备维修场景建议使用ConversationBufferWindowMemory(k=5),保留最近5轮对话即可。
6. 练习
- 基础 :用
TextLoader加载一份设备手册,用RecursiveCharacterTextSplitter分割,打印前3个块的内容 - 进阶 :构建
ConversationalRetrievalChain,连续提问3个相关问题,观察Memory的效果 - 挑战:对比自研RAG和LangChain RAG在相同5个问题上的回答质量和响应时间
7. 验证清单
- 能说出LangChain七大抽象各自的作用
- 成功用LangChain重构了设备维修RAG
- 理解
ConversationalRetrievalChain如何管理对话历史 - 能说明何时选LangChain、何时选LlamaIndex
- 能选择合适的Memory类型
code
python
# -*- coding: utf-8 -*-
"""
第11课:使用LangChain重构设备维修RAG系统
依赖: pip install langchain langchain-openai langchain-community langchain-classic chromadb
注: langchain 1.x 将 schema/text_splitter/chains/memory 迁至 langchain_classic 兼容包,
新项目推荐使用 langchain_core + langgraph 重写。
"""
import os
import sys
# === 配置 ===
os.environ.setdefault("OPENAI_API_KEY", "sk-your-key")
os.environ.setdefault("OPENAI_BASE_URL", "https://api.deepseek.com")
from langchain_classic.text_splitter import RecursiveCharacterTextSplitter
from langchain_classic.schema import Document
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain_classic.chains import ConversationalRetrievalChain
from langchain_classic.memory import ConversationBufferMemory
# === 设备维修知识库(模拟文档) ===
MAINTENANCE_DOCS = [
("液压系统维护手册", """液压系统是设备的核心动力部分。日常检查项目:
1. 液压油位检查:确保油位在标尺的2/3以上
2. 油温监测:正常工作温度40-60℃,超过70℃需停机检查
3. 滤芯更换:每工作500小时更换一次液压油滤芯
4. 密封件检查:每月检查各密封件,发现老化或渗漏立即更换
常见故障:液压泵异响通常由油液不足或滤芯堵塞引起。"""),
("电机维护手册", """电机维护要点:
1. 轴承润滑:每200工作小时加注润滑脂
2. 绝缘检测:每月用兆欧表测量绝缘电阻,不低于5MΩ
3. 散热检查:确保散热风扇运转正常,散热片无积尘
4. 接线检查:每季度紧固一次接线端子
电机过热故障处理:首先检查负载是否超载,其次检查通风是否良好,最后检查轴承是否磨损。"""),
("传动系统维护", """传动系统包括齿轮箱、皮带和链条:
1. 齿轮箱:每季度检查油位,每年更换齿轮油
2. 皮带传动:每月检查张紧度,发现裂纹立即更换
3. 链条传动:每周润滑,每月检查伸长量
传动异响诊断:低速异响多为齿轮磨损,高速异响多为轴承问题,变速时异响检查同步器。"""),
("设备润滑规范", """润滑是设备维护的基础工作:
- 润滑五定原则:定点、定质、定量、定期、定人
- 常用润滑剂:锂基脂(通用)、液压油(液压系统)、齿轮油(齿轮箱)
- 润滑记录:每次润滑后需填写润滑记录卡
- 油品存放:密封、避光、防潮,标识清晰"""),
("故障排查流程", """标准故障排查流程:
1. 收集信息:操作人员描述故障现象、发生时间、频率
2. 现场观察:查看设备状态、仪表读数、异常痕迹
3. 初步判断:根据现象缩小故障范围
4. 仪器检测:使用专业仪器测量关键参数
5. 确认故障:确定故障部位和原因
6. 制定方案:选择维修方案,准备工具和备件
7. 实施维修:按方案执行,注意安全规范
8. 验收测试:运行测试确认故障排除"""),
]
def build_knowledge_base():
"""构建设备维修知识库"""
print("📚 步骤1:加载文档...")
docs = [Document(page_content=content, metadata={"source": title})
for title, content in MAINTENANCE_DOCS]
print(f" 加载了 {len(docs)} 份文档")
print("📝 步骤2:文本分块...")
splitter = RecursiveCharacterTextSplitter(
chunk_size=300, chunk_overlap=40,
separators=["\n\n", "\n", "。", ";", " "]
)
chunks = splitter.split_documents(docs)
print(f" 分割为 {len(chunks)} 个知识块")
print("🔢 步骤3:向量化并存储...")
embeddings = OpenAIEmbeddings(
model="text-embedding-3-small",
openai_api_key=os.getenv("OPENAI_API_KEY"),
openai_api_base=os.getenv("OPENAI_BASE_URL"),
)
vectorstore = Chroma.from_documents(
documents=chunks, embedding=embeddings,
collection_name="maintenance_kb",
persist_directory="./chroma_maintenance"
)
print(" 向量库构建完成")
return vectorstore
def build_qa_chain(vectorstore):
"""构建带记忆的问答链"""
print("\n🔗 构建问答链...")
llm = ChatOpenAI(
model="deepseek-chat",
openai_api_key=os.getenv("OPENAI_API_KEY"),
openai_api_base=os.getenv("OPENAI_BASE_URL"),
temperature=0
)
memory = ConversationBufferMemory(
memory_key="chat_history", return_messages=True, output_key="answer"
)
qa_chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
memory=memory,
return_source_documents=True,
)
print(" 问答链就绪\n")
return qa_chain
def run_tests(qa_chain):
"""测试问答(与自研版本相同的问题)"""
test_queries = [
"液压油温度过高应该怎么处理?",
"电机轴承需要多久润滑一次?",
"设备出现故障后的标准排查流程是什么?",
]
print("=" * 50)
print("🤖 LangChain RAG 问答测试")
print("=" * 50)
for i, q in enumerate(test_queries, 1):
print(f"\n❓ 问题{i}:{q}")
try:
result = qa_chain({"question": q})
print(f"💡 回答:{result['answer'][:200]}")
if result.get("source_documents"):
src = result["source_documents"][0].metadata.get("source", "未知")
print(f"📖 来源:{src}")
except Exception as e:
print(f"⚠️ 出错:{e}")
# 测试多轮对话记忆
print("\n" + "=" * 50)
print("💬 多轮对话记忆测试")
print("=" * 50)
followup = [
("润滑五定原则是什么?", None),
("那液压油应该用什么类型的?", "上一轮问了润滑原则"),
]
for q, note in followup:
hint = f" ({note})" if note else ""
print(f"\n❓ {q}{hint}")
try:
result = qa_chain({"question": q})
print(f"💡 {result['answer'][:200]}")
except Exception as e:
print(f"⚠️ 出错:{e}")
def main():
print("🏭 第11课:LangChain重构设备维修RAG系统\n")
vectorstore = build_knowledge_base()
qa_chain = build_qa_chain(vectorstore)
run_tests(qa_chain)
print("\n✅ 测试完成!LangChain RAG系统运行正常。")
if __name__ == "__main__":
main()