LangChain教程 - RAG - PDF问答

系列文章索引
LangChain教程 - 系列文章

在现代自然语言处理(NLP)中,基于文档内容的问答系统变得愈发重要,尤其是当我们需要从大量文档中提取信息时。通过结合文档检索和生成模型(如RAG,Retrieval-Augmented Generation),我们可以构建强大的问答系统。本博客将详细介绍如何使用FastAPI和LangChain框架,创建一个基于PDF文档的RAG问答API。


一、背景

在许多实际应用中,用户可能需要基于大量的PDF文件进行快速的问答查询。LangChain作为一个强大的框架,支持将各种数据源与生成模型集成,而FastAPI则是一个轻量级的Web框架,适用于构建高性能的API。在本案例中,我们将使用FastAPI作为API服务端,LangChain来处理文档加载、文本切分、向量存储和问答生成任务。

二、技术栈

  • FastAPI:用于构建Web服务。
  • LangChain:提供构建问答系统的工具,涉及文档加载、文本切分、向量存储、RAG链构建等功能。
  • Ollama Embeddings:用于将文本转换为向量。
  • Chroma:用于存储和检索文本向量的数据库。
  • Starlette:FastAPI的底层库,用于支持流式响应。

三、实现步骤

1. 环境配置

首先,我们需要安装必需的库。你可以通过以下命令来安装:

pip install fastapi langchain langchain-chroma langchain-ollama langchain-community starlette uvicorn nest_asyncio

安装完毕后,我们可以开始构建我们的API。

2. 加载PDF并处理文本

我们从一个PDF文件加载文档,并将其切分成适合处理的小块。这样可以更高效地将文本转化为向量,并存储到数据库中。

from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter# 设置PDF文件路径
pdf_path = "../../files/pdf/en/Transformer.pdf"# 加载PDF文档并分割文本
loader = PyPDFLoader(pdf_path)
docs = loader.load()# 使用递归文本切分器来切分文档
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
splits = text_splitter.split_documents(docs)

在这段代码中,PyPDFLoader被用来加载PDF文件,而RecursiveCharacterTextSplitter则将文档切分为多个小块,确保每个块之间有200个字符的重叠,以便保持上下文的连贯性。

3. 存储向量到数据库

接下来,我们使用Chroma来存储文档的向量表示。我们利用OllamaEmbeddings模型将文本块转化为向量,并将它们存储在Chroma数据库中。

from langchain_chroma import Chroma
from langchain_ollama import OllamaEmbeddings# 存储分割后的文档到向量数据库
vectorstore = Chroma.from_documents(documents=splits, embedding=OllamaEmbeddings(model="nomic-embed-text"))

这里,我们通过OllamaEmbeddings将文档切分块转换为嵌入向量,并使用Chroma将这些向量存储到数据库中。这样,我们就可以通过相似度检索来快速找到与用户查询相关的文档。

4. 构建检索器

为了支持从数据库中检索相关文档,我们将构建一个基于相似度搜索的检索器。

# 构建检索器
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 3})

这段代码通过as_retriever方法创建了一个检索器,能够基于向量的相似度从数据库中返回与查询最相关的文档。

5. 定义RAG链

我们使用RAG(检索增强生成)技术,将检索到的文档与生成模型(如ChatOllama)结合,生成最终的答案。hub.pull("rlm/rag-prompt")方法提供了一个预定义的RAG提示模板。

from langchain import hub
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_ollama import ChatOllama# 定义RAG提示模板
prompt = hub.pull("rlm/rag-prompt")# 格式化检索到的文档
def format_docs(docs):return "\n\n".join(doc.page_content for doc in docs)# 定义RAG链
rag_chain = ({"context": retriever | format_docs, "question": RunnablePassthrough()}| prompt| ChatOllama(model="deepseek-r1:7b")| StrOutputParser()
)

在这里,我们将检索到的文档内容格式化为字符串,并将其与用户的查询一起传递到生成模型中,以生成最终的答案。

6. 生成答案和流式响应

我们定义了两个方法:一个是生成完整答案,另一个是生成流式响应。

import json# 生成答案函数
async def generate_answer(question: str):response = await rag_chain.ainvoke(question)return response# 生成流式响应
async def generate_streaming_response(question: str):async for chunk in rag_chain.astream(question):  # 使用astream逐块获取响应yield json.dumps({"answer chunk": chunk}) + "\n"  # 按流式返回每一块内容

在这部分代码中,generate_answer方法会返回完整的答案,而generate_streaming_response方法则返回流式响应,每次返回一个内容块。

7. 创建FastAPI应用

最后,我们使用FastAPI创建一个Web应用,提供一个POST接口来接收用户查询,并返回答案。

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from starlette.responses import StreamingResponse# 创建FastAPI应用
app = FastAPI()# 定义输入模型
class QueryModel(BaseModel):question: strstream: bool = False  # 默认不流式返回# 创建POST路由处理查询
@app.post("/query/")
async def query_question(query: QueryModel):try:if query.stream:# 如果stream为True,使用流式响应return StreamingResponse(generate_streaming_response(query.question), media_type="text/json")else:# 否则直接返回完整答案answer = await generate_answer(query.question)  # 使用await获取完整的答案return {"answer": answer}except Exception as e:raise HTTPException(status_code=500, detail=str(e))

此API提供了一个接口,用户可以通过发送带有问题的POST请求来获取答案。如果请求中指定streamTrue,系统将返回流式的答案。

8. 启动(jupyter)

notebooks下载地址: https://github.com/flower-trees/langchain-example/blob/master/pdf/jupyter/chat_pdf_api.ipynb

import nest_asyncio
import uvicorn
nest_asyncio.apply()
uvicorn.run(app, host="127.0.0.1", port=8000)

9. 提问

curl -X POST http://127.0.0.1:8000/query/ \-H "Content-Type: application/json" \-d '{"question": "Why is masking necessary in the decoder’s self-attention mechanism?","stream": true}'

10. 清理向量数据库

vectorstore.delete_collection()

四、完整代码实例

代码下载地址: https://github.com/flower-trees/langchain-example/blob/master/pdf/chat_pdf_api.py

import json
from contextlib import asynccontextmanagerfrom fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from langchain import hub
from langchain_chroma import Chroma
from langchain_community.document_loaders import PyPDFLoader
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_ollama import ChatOllama, OllamaEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from starlette.responses import StreamingResponse# 设置 PDF 文件路径
pdf_path = "../files/pdf/en/Transformer.pdf"# 加载 PDF 文档并分割文本
loader = PyPDFLoader(pdf_path)
docs = loader.load()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
splits = text_splitter.split_documents(docs)# 存储分割后的文档到向量数据库
vectorstore = Chroma.from_documents(documents=splits, embedding=OllamaEmbeddings(model="nomic-embed-text"))# 构建检索器
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 3})# 定义 RAG 提示模板
prompt = hub.pull("rlm/rag-prompt")# 格式化检索到的文档
def format_docs(docs):return "\n\n".join(doc.page_content for doc in docs)# 定义 RAG 链
rag_chain = ({"context": retriever | format_docs, "question": RunnablePassthrough()}| prompt| ChatOllama(model="deepseek-r1:7b")| StrOutputParser()
)print("RAG ready")# 生成答案函数
async def generate_answer(question: str):response = await rag_chain.ainvoke(question)return response# 生成流式响应
async def generate_streaming_response(question: str):async for chunk in rag_chain.astream(question):  # 使用 astream 逐块获取响应yield json.dumps({"answer chunk": chunk}) + "\n"  # 按流式返回每一块内容# 8. 清理向量数据库
def clear_vectorstore():vectorstore.delete_collection()@asynccontextmanager
async def lifespan(app: FastAPI):# 在应用启动时执行的代码yield# 在应用关闭时执行的代码clear_vectorstore()print("Vectorstore cleaned up successfully!")# 创建 FastAPI 应用
app = FastAPI(lifespan=lifespan)# 定义输入模型
class QueryModel(BaseModel):question: strstream: bool = False  # 默认不流式返回# 创建 POST 路由处理查询
@app.post("/query/")
async def query_question(query: QueryModel):try:if query.stream:# 如果 `stream` 为 True,使用流式响应return StreamingResponse(generate_streaming_response(query.question), media_type="text/json")else:# 否则直接返回完整答案answer = await generate_answer(query.question)  # 使用 await 获取完整的答案return {"answer": answer}except Exception as e:raise HTTPException(status_code=500, detail=str(e))# 启动 FastAPI 应用(适用于开发环境)
# uvicorn chat_pdf_api:app --reload

五、总结

通过本教程,我们展示了如何使用FastAPI和LangChain框架,结合检索增强生成(RAG)技术,构建一个基于PDF文档的问答系统。系统支持两种查询方式:普通的完整答案返回和流式答案返回。借助LangChain提供的强大工具集,我们能够轻松地实现文档加载、文本切分、向量存储与检索等功能。FastAPI则让我们能够高效地将这些功能封装为一个Web API,供用户使用。

在实际应用中,这种基于文档的问答系统可以广泛应用于客户支持、知识库管理、教育培训等领域,为用户提供智能化的答案生成服务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/71084.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大白话前端性能优化方法的分类与具体实现

大白话前端性能优化方法的分类与具体实现 一、资源加载优化 1. 压缩与合并文件 大白话解释: 咱们的网页代码里,就像一个房间堆满了东西,有很多没用的“杂物”,比如代码里的空格、注释啥的。压缩文件就是把这些“杂物”清理掉&a…

MySQL并发知识(面试高频)

mysql并发事务解决 不同隔离级别下,mysql解决并发事务的方式不同。主要由锁机制和MVCC(多版本并发控制)机制来解决并发事务问题。 1. mysql中的锁有哪些? 表级锁: 场景:表级锁适用于需要对整个表进行操作的情况,例如…

【Kubernets】K8S内部nginx访问Service资源原理说明

文章目录 原理概述**一、核心概念****二、Nginx 访问 Service 的流程****1. Service 的作用****2. Endpoint 的作用****3. Nginx Pod 发起请求****(1) DNS 解析****(2) 流量到达 kube-proxy****(3) 后端 Pod 处理请求** **三、不同代理模式的工作原理****1. iptables 模式****2…

HTML:自闭合标签简单介绍

1. 什么是自结束标签&#xff1f; 定义&#xff1a;自结束标签&#xff08;Self-closing Tag&#xff09;是指 不需要单独结束标签 的 HTML 标签&#xff0c;它们通过自身的语法结构闭合。语法形式&#xff1a; 在 HTML5 中&#xff1a;直接写作 <tag>&#xff0c;例如 …

《几何原本》公理

《几何原本》公理 等于同量的量彼此相等 即若 a b , b c ab,bc ab,bc 则 a c ac ac 等量加等量&#xff0c;其和仍相等 即若 a b ab ab 则 a c b c acbc acbc 等量减等量&#xff0c;其差仍相等 即若 a b ab ab 则 a − c b − c a-cb-c a−cb−c 彼此能够&a…

学习路程十一 langchain核心组件 Memory

前序 在最开始我们就通过实验知道LLM 本身是没有记忆的&#xff0c;每一次LLM的API调用都是一个全新的会话。但在某些应用程序中&#xff0c;如&#xff1a;聊天机器人&#xff0c;让LLM记住以前的历史交互是非常重要&#xff0c;无论是在短期的还是长期的。langchain中的“Me…

Spring-AI搭建企业专属知识库 一

环境介绍&#xff1a;Spring3.3.2 JDK 21 POM文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation&…

C语言多级指针详解 - 通过实例理解一级、二级、三级指针

C语言多级指针详解 - 通过实例理解一级、二级、三级指针 文章目录 C语言多级指针详解 - 通过实例理解一级、二级、三级指针引言一、示例代码二、指针基础回顾&#xff08;先备知识&#xff09;1.生活中的类比2.指针变量定义3.关键操作符4.解引用本质 三、多级指针的本质1.层级关…

Difyにおけるデータベースマイグレーション手順

Difyにおけるデータベースマイグレーション手順 はじめに主要マイグレーションコマンドAlembic直接コマンドFlask-Migrate経由コマンド 標準マイグレーション作業フローデプロイ時の対応注意事項 はじめに Difyプロジェクトでのデータベースマイグレーションには、Alembicコマ…

React + TypeScript 实现 SQL 脚本生成全栈实践

React TypeScript 实现数据模型驱动 SQL 脚本生成全栈实践 引言&#xff1a;数据模型与 SQL 的桥梁革命 在现代化全栈开发中&#xff0c;数据模型与数据库的精准映射已成为提升开发效率的关键。传统手动编写 SQL 脚本的方式存在模式漂移风险高&#xff08;Schema Drift&#…

Python 高精度计算利器:decimal 模块详解

Python 高精度计算利器&#xff1a;decimal 模块详解 在 Python 编程中&#xff0c;处理浮点数时&#xff0c;标准的 float 类型往往会因二进制表示的特性而产生精度问题。decimal 模块应运而生&#xff0c;它提供了十进制浮点运算功能&#xff0c;能让开发者在需要高精度计算…

freertos 学习5--链路状态设置

以下三个函数都是针对网卡netif的操作。1 netif_set_up函数(netif_set_down) 特点 直接操作网络接口&#xff0c;不通过 netifapi 机制。 适用于单线程环境&#xff0c;或者在多线程环境中需要额外加锁。1.1 netif_set_up 对比windows下面&#xff0c;相当于是在资源管理器中…

基于大数据的民宿旅馆消费数据分析系统

【大数据】基于大数据的民宿旅馆消费数据分析系统&#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 该系统可以揭示民宿市场的消费模式和价格分布情况&#xff0c;帮助理解消费者偏好、价格走势及…

线程池的介绍

线程池的分类 FixedThreadPool: 固定线程池&#xff1a;线程池中的线程数量固定&#xff0c;这些线程会一直存在&#xff0c;不会随任务的增 加或减少而动态调整&#xff0c;超出的任务会在队列中等待。 使用场景&#xff1a;任务量比较固定但耗时较长的任务。 CachedThreadPo…

[特殊字符]【CVPR2024新突破】Logit标准化:知识蒸馏中的自适应温度革命[特殊字符]

文章信息 题目&#xff1a;Logit Standardization in Knowledge Distillation论文地址&#xff1a;paper代码地址&#xff1a;code年份&#xff1a;2024年发表于CVPR 文章主题 文章的核心目标是改进知识蒸馏&#xff08;KD&#xff09;中的一个关键问题&#xff1a;传统KD方…

CAM350_安装

版本&#xff1a;V14.5 一、安装 打开.exe文件 选择不重启&#xff0c;然后再打开这个.exe 再来一次类似的操作 二、配置 复制patch文件夹中的这三个 &#xff0c;粘贴到掉安装目录中 设置ACT_INC_LICENSE_FILE用户环境变量来设置license管理 打开电脑的环境变量 破解完毕&am…

「Selenium+Python自动化从0到1②|2025浏览器操控7大核心API实战(附高效避坑模板))」

Python 自动化操作浏览器基础方法 在进行 Web 自动化测试时&#xff0c;操作浏览器是必不可少的环节。Python 结合 Selenium 提供了强大的浏览器操作功能&#xff0c;让我们能够轻松地控制浏览器执行各种任务。本文将详细介绍如何使用 Python 和 Selenium 操作浏览器的基本方法…

mysqldump 参数详解

mysqldump 是一个用于备份 MySQL 数据库的工具。它可以生成一组 SQL 语句,这些语句可以用来重现原始数据库对象定义和表数据。以下是一些常用的 mysqldump 参数及其详细解释: 常用参数 基本参数 --host=host_name, -h host_name: 指定 MySQL 数据库主机地址,默认为 localh…

【AD】3-9 物料BOM表的设置与导出

1.报告—… 2.筛选导出内容 3.进行选择导出格式 官方模板 亦可以自行修改其模板&#xff0c;AD官方的BOM表模板在安装目录下的Templates文件夹下&#xff08;C:\Users\Public\Documents\Altium\AD24\Templates&#xff09;

数据结构——布隆过滤器

目录 布隆过滤器概念 布隆过滤器实现 哈希函数 布隆过滤器类 加入到布隆过滤器 判断在不在 测试一下 为啥不写删除&#xff1f; 测试一下误判率 布隆过滤器概念 布隆过滤器也是一种位图结构&#xff0c;它可以快速的判断字符串在不在位图中。它的优点是节省空间。 …