当前位置: 首页 > news >正文

LangChain部署RAG part2.搭建多模态RAG引擎(赋范大模型社区公开课听课笔记)

从零到一搭建多模态RAG引擎:代码解析与实战指南

一、多模态RAG系统核心技术栈

在开始编码前,需先明确整个系统的技术选型。本项目围绕“PDF解析→结构化转换→向量检索→智能问答”全流程设计,核心工具链如下:

技术模块核心工具作用
文档解析与预处理Unstructured + PaddleOCR提取PDF中的标题、段落、表格、图片等元素,支持中英文OCR
PDF渲染与图片处理PyMuPDF(fitz)、Pillow读取PDF页面、提取图片并转换色彩空间(如CMYK转RGB)
结构化转换html2text、Markdown处理将表格HTML转为Markdown,生成结构化中间文档
向量数据库FAISS + OpenAI Embeddings存储文本向量,实现高效相似性检索
智能体开发LangChain + LangGraph构建多节点工作流,实现“检索-评估-回答”自动化逻辑
前端交互Agent Chat UI提供可视化对话界面,支持多模态结果展示

二、环境搭建:从依赖安装到配置

环境准备是项目开发的基础,需确保Python版本兼容性与依赖包正确性,以下是详细步骤:

1. 创建虚拟环境

推荐使用Python 3.9~3.11版本(本文以3.10为例),通过condavenv创建独立虚拟环境,避免依赖冲突:

# 方式1:使用conda创建
conda create -n pdf_rag python=3.10 -y
conda activate pdf_rag# 方式2:使用venv创建
python -m venv pdf_rag
pdf_rag\Scripts\activate  # Windows系统
# source pdf_rag/bin/activate  # Linux/Mac系统

2. 安装核心依赖

通过pip安装文档解析、OCR、向量处理等所需依赖,可指定华为/清华镜像源加速下载:

# 1. 文档解析核心库(支持PDF/Word/PPT等)
pip install "unstructured[all-docs]" --index-url https://mirrors.huaweicloud.com/repository/pypi/simple# 2. OCR引擎(PaddleOCR比Tesseract更优,支持中英文)
pip install paddlenlp paddleocr# 3. PDF与图片处理库
pip install PyMuPDF pillow matplotlib# 4. 结构化转换与向量数据库
pip install html2text faiss-cpu langchain-text-splitters# 5. LangChain生态(智能体开发)
pip install langchain-core langchain-community langchain-openai langgraph langsmith# 6. 环境变量与工具调用
pip install python-dotenv langchain-tavily

3. 配置环境变量

创建.env文件,存储API密钥(如OpenAI、DeepSeek)与LangSmith追踪配置,保障敏感信息安全:

# .env文件内容
DEEPSEEK_API_KEY=sk-c1a253xxxxxx  # 替换为你的DeepSeek API密钥
OPENAI_API_KEY=sk-proj-gExxxxxx   # 替换为你的OpenAI API密钥
LANGSMITH_TRACING=true            # 开启LangSmith流程追踪
LANGSMITH_API_KEY=lsv2_pt_b44xxxx # 替换为你的LangSmith API密钥
LANGSMITH_PROJECT=langraph_studio_chatbot  # LangSmith项目名
TAVILY_API_KEY=tvly-27xxxxxx      # 可选,用于Web搜索工具

三、PDF文档解析:从非结构化到结构化

多模态RAG的核心第一步是将PDF中的多类型元素(文本、表格、图片)提取并结构化。本项目采用“Unstructured + PaddleOCR”方案,实现高精度解析与重建,具体代码如下:

1. 提取PDF元素(文本、表格、图片元数据)

使用UnstructuredLoader加载PDF,通过hi_res模式(高分辨率OCR)处理复杂排版,同时开启表格结构检测:

from langchain_unstructured import UnstructuredLoader# 1. 配置PDF路径与解析参数
file_path = "0.LangChain技术生态介绍.pdf"  # 你的PDF文件路径
loader_local = UnstructuredLoader(file_path=file_path,strategy="hi_res",               # 高分辨率模式,适合复杂文档infer_table_structure=True,      # 自动解析表格结构ocr_languages="chi_sim+eng",     # 支持中英文OCRocr_engine="paddleocr"           # 指定PaddleOCR引擎
)# 2. 逐页加载并提取元素(返回LangChain Document对象列表)
docs_local = []
for doc in loader_local.lazy_load():docs_local.append(doc)# 3. 查看解析结果(每个doc包含文本内容与元数据)
print("解析元素类型:", [doc.metadata["category"] for doc in docs_local[:5]])
print("第1个元素内容:", docs_local[0].page_content)
print("第1个元素元数据(页码、坐标):", docs_local[0].metadata)
  • 关键说明docs_local中的每个Document对象包含page_content(文本内容)与metadata(页码、元素类型、坐标、置信度等),为后续结构化提供基础。

2. 可视化解析结果(可选)

为验证解析准确性,可通过PyMuPDF渲染PDF页面,并绘制元素边界框(标题用紫色、表格用红色、图片用绿色):

import fitz
import matplotlib.patches as patches
import matplotlib.pyplot as plt
from PIL import Imagedef plot_pdf_with_boxes(pdf_page, segments):"""绘制PDF页面与元素边界框"""# 1. 将PDF页面转为PIL图片pix = pdf_page.get_pixmap()pil_image = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)# 2. 初始化绘图fig, ax = plt.subplots(1, figsize=(10, 10))ax.imshow(pil_image)# 3. 定义元素颜色映射category_to_color = {"Title": "orchid",       # 标题:紫色"Image": "forestgreen",  # 图片:绿色"Table": "tomato"        # 表格:红色}categories = set()# 4. 遍历元素,绘制边界框for segment in segments:# 坐标缩放:将PDF逻辑坐标转为图片像素坐标points = segment["coordinates"]["points"]layout_width = segment["coordinates"]["layout_width"]layout_height = segment["coordinates"]["layout_height"]scaled_points = [(x * pix.width / layout_width, y * pix.height / layout_height)for x, y in points]# 确定颜色(未定义类型默认蓝色)box_color = category_to_color.get(segment["category"], "deepskyblue")categories.add(segment["category"])# 绘制多边形框(通常为矩形)rect = patches.Polygon(scaled_points, linewidth=1, edgecolor=box_color, facecolor="none")ax.add_patch(rect)# 5. 添加图例与隐藏坐标轴legend_handles = [patches.Patch(color="deepskyblue", label="Text")]for category in ["Title", "Image", "Table"]:if category in categories:legend_handles.append(patches.Patch(color=category_to_color[category], label=category))ax.axis("off")ax.legend(handles=legend_handles, loc="upper right")plt.tight_layout()plt.show()def render_page(doc_list: list, page_number: int, print_text=True):"""渲染指定页码的PDF与元素"""# 1. 打开PDF并加载指定页面pdf_page = fitz.open(file_path).load_page(page_number - 1)  # 页码从0开始# 2. 筛选该页面的所有元素page_docs = [doc for doc in doc_list if doc.metadata.get("page_number") == page_number]segments = [doc.metadata for doc in page_docs]# 3. 绘制与打印文本plot_pdf_with_boxes(pdf_page, segments)if print_text:for doc in page_docs:print(f"【{doc.metadata['category']}{doc.page_content}\n")# 调用函数,渲染第1页
render_page(docs_local, page_number=1)

3. PDF逆向转化为Markdown

将解析后的元素组装为Markdown文档(保留标题层级、表格结构、图片引用),生成可直接用于RAG的结构化数据:

import os
import fitz
from unstructured.partition.pdf import partition_pdf
from html2text import html2text# 1. 配置路径
pdf_path = "0.LangChain技术生态介绍.pdf"
output_dir = "pdf_images"  # 存储提取的图片
os.makedirs(output_dir, exist_ok=True)  # 不存在则创建文件夹# 2. 提取PDF元素(文本、表格、图片元数据)
elements = partition_pdf(filename=pdf_path,infer_table_structure=True,strategy="hi_res",ocr_languages="chi_sim+eng",ocr_engine="paddleocr"
)# 3. 提取图片并保存(处理CMYK转RGB)
doc = fitz.open(pdf_path)
image_map = {}  # 映射:页码 -> 图片路径列表
for page_num, page in enumerate(doc, start=1):image_map[page_num] = []# 遍历页面中的所有图片for img_index, img in enumerate(page.get_images(full=True), start=1):xref = img[0]  # 图片引用IDpix = fitz.Pixmap(doc, xref)# 图片保存路径(按页码+索引命名)img_path = os.path.join(output_dir, f"page{page_num}_img{img_index}.png")# 处理色彩空间:CMYK转RGBif pix.n < 5:  # RGB/Gray模式pix.save(img_path)else:  # CMYK模式pix = fitz.Pixmap(fitz.csRGB, pix)pix.save(img_path)image_map[page_num].append(img_path)# 4. 组装Markdown内容
md_lines = []
inserted_images = set()  # 避免重复插入图片for el in elements:cat = el.category  # 元素类型text = el.text     # 元素文本page_num = el.metadata.page_number  # 元素所在页码# 处理标题(一级标题#,二级标题##)if cat == "Title" and text.strip().startswith("- "):md_lines.append(text + "\n")  # 列表项标题,保持原样elif cat == "Title":md_lines.append(f"# {text}\n")elif cat in ["Header", "Subheader"]:md_lines.append(f"## {text}\n")# 处理表格(HTML转Markdown)elif cat == "Table":if hasattr(el.metadata, "text_as_html") and el.metadata.text_as_html:md_lines.append(html2text(el.metadata.text_as_html) + "\n")else:md_lines.append(el.text + "\n")# 处理图片(引用本地保存的图片)elif cat == "Image":for img_path in image_map.get(page_num, []):if img_path not in inserted_images:md_lines.append(f"![Image](./{img_path})\n")inserted_images.add(img_path)# 处理普通文本(段落、列表等)else:md_lines.append(text + "\n")# 5. 写入Markdown文件
output_md = "LangChain技术生态介绍【逆向转化版】.md"
with open(output_md, "w", encoding="utf-8") as f:f.write("\n".join(md_lines))print(f"✅ 转换完成!生成文件:{output_md},图片保存至:{output_dir}")

四、向量数据库构建:文本切片与嵌入

结构化的Markdown文档需进一步切片为“语义片段”,并通过嵌入模型转为向量,存储到FAISS数据库中,为后续检索提供支持:

1. 文本切片(Markdown标题分层)

使用MarkdownHeaderTextSplitter按标题层级切片(如一级标题#、二级标题##),避免切断语义逻辑:

from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import MarkdownHeaderTextSplitter
from langchain_community.vectorstores import FAISS
import os# 1. 加载环境变量与嵌入模型
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
embed = OpenAIEmbeddings(api_key=OPENAI_API_KEY,base_url="https://ai.devtool.tech/proxy/v1",  # 可选,代理地址model="text-embedding-3-small"  # 轻量型嵌入模型,性价比高
)# 2. 读取Markdown文件
md_path = "LangChain技术生态介绍【逆向转化版】.md"
with open(md_path, "r", encoding="utf-8") as f:md_content = f.read()# 3. 按标题分层切片
headers_to_split_on = [("#", "Header 1"),   # 一级标题:#("##", "Header 2")   # 二级标题:##
]
markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
md_splits = markdown_splitter.split_text(md_content)  # 切片后的Document列表# 4. 查看切片结果
print(f"切片总数:{len(md_splits)}")
print(f"前3个切片标题:")
for i, split in enumerate(md_splits[:3]):print(f"  {i+1}. {split.metadata.get('Header 1', '')} - {split.metadata.get('Header 2', '')}")

2. 向量存储与本地保存

将切片后的文本转为向量,存储到FAISS数据库,并保存到本地(便于后续加载复用):

# 1. 构建FAISS向量库
vector_store = FAISS.from_documents(md_splits, embedding=embed)# 2. 本地保存向量库(文件夹形式)
vs_save_path = "langchain_course_db"
vector_store.save_local(vs_save_path)
print(f"✅ 向量库已保存至:{vs_save_path}")# 3. 加载向量库(后续使用时)
loaded_vector_store = FAISS.load_local(folder_path=vs_save_path,embeddings=embed,allow_dangerous_deserialization=True  # 允许反序列化(本地文件安全时使用)
)
print("✅ 向量库加载成功,可用于检索!")

五、Agentic RAG系统开发:基于LangGraph的智能问答

传统RAG仅能“检索→回答”,而Agentic RAG通过多节点工作流(如“检索评估→问题改写→重新检索”)提升问答准确性。本项目基于LangGraph构建智能体,实现端到端多模态问答:

1. 核心代码:LangGraph工作流定义

from __future__ import annotations
import os
import asyncio
from typing import Literal
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain.tools.retriever import create_retriever_tool
from langgraph.graph import MessagesState, StateGraph, START, END
from langgraph.prebuilt import ToolNode
from pydantic import BaseModel, Field# 1. 加载环境变量
load_dotenv(override=True)# 2. 初始化LLM与嵌入模型
MODEL_NAME = "deepseek-chat"  # 主模型(DeepSeek对话模型)
# 主模型:用于对话与工具调用决策
model = init_chat_model(model=MODEL_NAME,model_provider="deepseek",temperature=0  # 0表示确定性输出,适合问答
)
# 评估模型:判断检索结果是否相关
grader_model = init_chat_model(model=MODEL_NAME,model_provider="deepseek",temperature=0
)
# 嵌入模型:与向量库一致
embed = OpenAIEmbeddings(api_key=os.getenv("OPENAI_API_KEY"),base_url="https://ai.devtool.tech/proxy/v1",model="text-embedding-3-small"
)# 3. 加载向量库并创建检索工具
VS_PATH = "langchain_course_db"  # 向量库路径
vector_store = FAISS.load_local(folder_path=VS_PATH,embeddings=embed,allow_dangerous_deserialization=True
)
# 创建检索工具(每次返回3条相关结果)
retriever_tool = create_retriever_tool(vector_store.as_retriever(search_kwargs={"k": 3}),name="retrieve_langchain_course",  # 工具名(需唯一)description="检索LangChain技术生态课程的相关内容,包括工具链、Agent开发、RAG集成等。"  # 工具描述
)# 4. 定义Prompt(指令设计是智能体核心)
# 系统指令:限定回答范围与工具调用逻辑
SYSTEM_INSTRUCTION = ("你是LangChain技术生态课程的助教,仅回答与LangChain相关的问题(如工具链、Agent开发、RAG集成)。\n""如果问题与课程无关,直接回复:'我不能回答与LangChain技术生态课程无关的问题。'\n""当现有上下文不足时,可调用工具`retrieve_langchain_course`获取相关资料。"
)
# 评估Prompt:判断检索结果是否与问题相关
GRADE_PROMPT = ("你是检索结果评估师,需判断文档是否与用户问题相关。\n""检索文档:\n{context}\n\n""用户问题:{question}\n""仅返回'yes'(相关)或'no'(不相关),无需额外内容。"
)
# 问题改写Prompt:优化不相关/模糊的问题
REWRITE_PROMPT = ("你是问题优化师,需将用户问题改写为与LangChain技术生态相关的清晰问题。\n""例如:'如何开发智能体?' → '如何基于LangChain开发Agent智能体?'\n""原始问题:\n{question}\n""优化后问题:"
)
# 回答Prompt:基于上下文生成结构化回答
ANSWER_PROMPT = ("你是LangChain课程助教,需基于提供的上下文回答问题,要求:\n""1. 用Markdown格式,代码块用```包裹,图片引用保留路径;\n""2. 若上下文无相关信息,直接回复'我不知道。'\n""3. 优先引用课程中的代码示例与概念。\n\n""问题:{question}\n""上下文:{context}"
)# 5. 定义LangGraph节点(每个节点对应一个功能)
class GradeDoc(BaseModel):"""结构化输出:评估结果(yes/no)"""binary_score: str = Field(description="检索结果相关性,'yes'表示相关,'no'表示不相关")async def generate_query_or_respond(state: MessagesState):"""节点1:判断是否调用检索工具(或直接回答)"""response = await model.bind_tools([retriever_tool]).ainvoke([{"role": "system", "content": SYSTEM_INSTRUCTION},*state["messages"]  # 历史消息(含用户问题)])return {"messages": [response]}async def grade_documents(state: MessagesState) -> Literal["generate_answer", "rewrite_question"]:"""节点2:评估检索结果,决定生成回答或改写问题"""# 提取用户问题与检索结果question = state["messages"][0].content  # 第1条消息是原始问题context = state["messages"][-1].content  # 最后1条消息是检索结果# 调用评估模型prompt = GRADE_PROMPT.format(question=question, context=context)result = await grader_model.with_structured_output(GradeDoc).ainvoke([{"role": "user", "content": prompt}])# 返回下一个节点(相关则生成回答,否则改写问题)return "generate_answer" if result.binary_score.lower() == "yes" else "rewrite_question"async def rewrite_question(state: MessagesState):"""节点3:改写问题(使其更贴合课程内容)"""question = state["messages"][0].contentprompt = REWRITE_PROMPT.format(question=question)resp = await model.ainvoke([{"role": "user", "content": prompt}])# 将改写后的问题作为新的用户消息,回到工具调用决策节点return {"messages": [{"role": "user", "content": resp.content}]}async def generate_answer(state: MessagesState):"""节点4:基于上下文生成最终回答"""question = state["messages"][0].contentcontext = state["messages"][-1].contentprompt = ANSWER_PROMPT.format(question=question, context=context)resp = await model.ainvoke([{"role": "user", "content": prompt}])return {"messages": [resp]}# 6. 构建LangGraph工作流(图结构定义流程逻辑)
workflow = StateGraph(MessagesState)  # 状态类型:消息列表# 添加节点
workflow.add_node("generate_query_or_respond", generate_query_or_respond)  # 工具调用决策
workflow.add_node("retrieve", ToolNode([retriever_tool]))  # 检索工具节点
workflow.add_node("rewrite_question", rewrite_question)  # 问题改写
workflow.add_node("generate_answer", generate_answer)  # 生成回答# 定义边(流程逻辑)
workflow.add_edge(START, "generate_query_or_respond")  # 起点→决策节点
workflow.add_edge("generate_query_or_respond", "retrieve")  # 决策→检索
workflow.add_conditional_edges(  # 检索后→评估,分支到回答或改写"retrieve",grade_documents  # 条件函数:返回下一个节点名
)
workflow.add_edge("generate_answer", END)  # 回答→终点
workflow.add_edge("rewrite_question", "generate_query_or_respond")  # 改写→重新决策# 7. 编译智能体(生成可调用对象)
rag_agent = workflow.compile(name="langchain_rag_agent")
print("✅ Agentic RAG智能体编译完成!")

2. 智能体调用与测试

通过ainvoke异步调用智能体,支持多轮对话与工具自动调用:

# 测试函数:调用智能体并打印结果
async def test_rag_agent(question: str):print(f"用户问题:{question}")print("-" * 50)# 调用智能体(输入:消息列表,输出:更新后的消息列表)result = await rag_agent.ainvoke({"messages": [{"role": "user", "content": question}]})# 提取最终回答(最后一条消息)answer = result["messages"][-1].contentprint(f"智能体回答:\n{answer}")print("=" * 50 + "\n")# 测试案例1:相关问题(需检索)
await test_rag_agent("如何基于LangChain构建RAG系统?")# 测试案例2:不相关问题(直接拒绝)
await test_rag_agent("如何学习Python基础?")# 测试案例3:模糊问题(需改写)
await test_rag_agent("如何开发智能体?")

六、前端部署:Agent Chat UI可视化

为让系统更易用,可通过LangChain官方的agent-chat-ui搭建前端界面,支持可视化对话与工具调用记录查看:

# 1. 克隆前端仓库
git clone https://github.com/langchain-ai/agent-chat-ui.git# 2. 进入目录并安装依赖(需Node.js 16+)
cd agent-chat-ui
pnpm install  # 或 npm install# 3. 配置后端地址(需将Python智能体部署为API)
# 修改agent-chat-ui/src/lib/agent.ts中的API地址,指向你的后端接口# 4. 启动前端
pnpm dev
# 访问 http://localhost:3000 即可使用对话界面

七、总结与扩展

本文通过完整代码实现了多模态RAG引擎的核心流程,从PDF解析到智能问答,覆盖“数据处理→向量存储→智能体开发”全链路。开发者可基于此扩展:

  1. 多模态支持增强:集成InternVL等模型,实现图片内容理解(如识别图表中的数据);
  2. 企业级部署:将向量库替换为Milvus/Weaviate,支持更大数据量;使用Docker容器化前后端,实现高可用;
  3. 功能优化:添加对话记忆(Memory)、多工具调用(如Web搜索、SQL查询)、结果溯源(引用原始文档页码)。

如需深入学习大模型Agent开发,可参考课程《2025大模型Agent智能体开发实战》,获取更多企业级案例与进阶技术。

http://www.dtcms.com/a/452830.html

相关文章:

  • SSM--day4--SpringMVC(补充)
  • Flink Checkpoint与反压问题排查手册:从日志分析到根因定位
  • 元宇宙的教育应用:重构学习体验与知识传递
  • 建设99网站江西网站开发哪家好
  • RabbitMQ高可用集群搭建教程(基于CentOS 7.9 + Erlang 23.2.7 + RabbitMQ 3.8.8)
  • 【LangChain】P14 LangChain 输出解析器深度解析:Json解析器、XML解析器、字符串及列表、日期解析器
  • 仿真软件-多机器人2
  • 《基于 ERT 的稀疏电极机器人皮肤技术》ICRA2020论文解析
  • 聚焦CRISPR技术配套工具链的开源生态建设
  • 网站做视频窗口接口收费么免费搭建自己的网站
  • ​​Avalonia UI 开发核心注意事项:从理念到部署的避坑指南​
  • 从chatGPT获取的关于相机焦距与其他参数的关系
  • 拒绝做网站的理由wordpress自适应 slide
  • 【IT老齐456】Spring Boot优雅开发多线程应用,笔记01
  • 网站收录怎么弄极路由4 做网站
  • 备考华为HCIA - 云计算,培训与自学到底该怎么选?
  • 106、23种设计模式之备忘录模式(15/23)
  • LangChain部署rag Part3olmOCR与MinerU工具(赋范大模型社区公开课听课笔记)
  • C++进阶:使用普通函数重载算数运算符
  • 从内核调优到集群部署:基于Linux环境下KingbaseES数据库安装指南
  • Micro850 控制器深度解析:硬件特性与 I/O 接线核心(罗克韦尔2)
  • Python oct() 函数
  • (一) 机器学习之深度神经网络
  • C语言指针全面解析:从内存管理到高级应用
  • 南通网站建设推广专家建站教程的优点
  • Spring Boot整合Apache Shiro权限认证框架(应用篇)
  • 杰理AC632N---RTC应用问题
  • 网站免费软件下载阳江人社局官网招聘
  • 第二十三章:解析天书,诠释法则——Interpreter的解释艺术
  • 论文阅读-FoundationStereo