摄取就是将文档转换为计算机可以理解和分析的数字,并将其存储在特殊类型的数据库中以便有效检索的过程。这些数字在形式上被称为嵌入,这种特殊类型的数据库被称为向量存储。
提取文本
分块
嵌入
向量存储
# 使用TextLoader类将来自不同来源的数据加载到由文本和相关元数据组成的document类
from langchain_community.document_loaders import TextLoader
# 换成自己文件地址
loader = TextLoader('./prompt/summarize.txt', encoding="utf-8")
docs = loader.load()
print(docs)
# 前提:pip install beautifulsoup4
# 用WebBaseLoader从web url加载HTML并将其解析为文本
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader('https://www.langchain.com/')
docs = loader.load()
print(docs)
# 使用PDFLoader从PDF文档中提取文本
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader('./test/test.pdf')
pages = loader.load()
print(pages)
RecursiveCharacterTextSplitter类实现了
按重要性排列一个分隔符列表。默认情况下,它们是:
a.段落分离器:\n\n
b.行分离器:\n
c.字分离器:空格字符
为了遵循给定的块大小,例如1000个字符,首先拆分段落。
对于任何超过所需块大小的段落,用下一个分离器分隔:lines。继续执行,直到所有块都小于所需长度,或者没有其他分隔符可以尝试。
将每个块作为Document发出,并传入原始文档的元数据和关于原始文档中位置的附加信息。
# 用RecursiveCharacterTextSplitter对文件进行分块
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader
loader = TextLoader('./prompt/summarize.txt', encoding="utf-8")
docs = loader.load()
# chunk_size表示允许的最大的分块长度
# chunk_overlep分块之中的重叠部分
splitter = RecursiveCharacterTextSplitter(chunk_size=50, chunk_overlap=20)
splitted_docs = splitter.split_documents(docs)
print(splitted_docs)
RecursiveCharacterTextSplitter包含了许多流行语言的分隔符,比如Python、JS、Markdown、HTML等等,并实现例如,每个函数体都保持在同一个块中等分块策略。
# 对源代码进行拆分
from langchain_text_splitters import (
Language,
RecursiveCharacterTextSplitter,
)
PYTHON_CODE = """ def hello_world(): print("Hello, World!") # Call the function hello_world() """
# 使用from_language()创建实例
python_splitter = RecursiveCharacterTextSplitter.from_language(
language=Language.PYTHON, chunk_size=50, chunk_overlap=0
)
python_docs = python_splitter.create_documents([PYTHON_CODE])
print(python_docs)
# 对markdown进行拆分
from langchain_text_splitters import (
Language,
RecursiveCharacterTextSplitter,
)
markdown_text = """ # LangChain ⚡ Building applications with LLMs through composability ⚡ ## Quick Install ```bash pip install langchain ``` As an open source project in a rapidly developing field, we are extremely open to contributions. """
# 文本沿着Markdown文档中的自然停止点拆分;例如,标题放在一个块中,标题下的文本行放在另一个块中,依此类推。
# 对markdown和源代码进行拆分后返回的是一个特殊对象,所以要用到create_documents再进行处理
# 特殊对象
md_splitter = RecursiveCharacterTextSplitter.from_language(
language=Language.MARKDOWN, chunk_size=60, chunk_overlap=0
)
# create_documents方法
# 第一个参数是要进行切分的数据
# 第二个参数是要传入的元数据 类似于来源、文件名之列的数据
md_docs = md_splitter.create_documents(
[markdown_text], [{"source": "https://www.langchain.com"}])
print(md_docs)
Embeddings类,用于与文本嵌入模型交互,并生成文本的矢量表示。该类提供了两个方法:一个用于嵌入文档,另一个用于嵌入查询。前者接受文本字符串列表作为输入,而后者接受单个文本字符串。
# 翻译过程中我使用了开源的嵌入工具 如果你需要使用API 请自行替换嵌入模型
from langchain_openai import OpenAIEmbeddings
model = OpenAIEmbeddings()
embeddings = model.embed_documents([
"Hi there!",
"Oh, hello!",
"What's your name?",
"My friends call me World",
"Hello World!"
])
# 同时嵌入多个文档
from langchain_huggingface import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2" # 高效的语义模型
)
# 输入文本
texts = ["你好,世界。", "LangChain 是一个强大的工具。"]
# 生成嵌入向量
vectors = embeddings.embed_documents(texts)
# 打印结果
for idx, vector in enumerate(vectors):
print(f"文本 {idx + 1}: {texts[idx]}")
print(f"嵌入向量: {vector[:5]}... (维度: {len(vector)})\n")
# 批量进行嵌入操作
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_ollama import OllamaLLM
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 引用模型
deepseek = OllamaLLM(
model="deepseek-r1:32b",
)
# 加载文档
loader = TextLoader("./test/deeplearning.txt", encoding="utf-8")
doc = loader.load()
# 文档分割
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
chunks = splitter.split_documents(doc)
# 生成嵌入
embeddings_model = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2"
)
embeddings = embeddings_model.embed_documents(
[chunk.page_content for chunk in chunks]
)
print(embeddings)
# 需要安装docker
# 运行下面命令 在6024端口启动Postgres
docker run \
--name pgvector-container \
-e POSTGRES_USER=langchain \
-e POSTGRES_PASSWORD=langchain \
-e POSTGRES_DB=langchain \
-p 6024:5432 \
-d pgvector/pgvector:pg16
# 可在docker仪器表观察运行
# 重要连接字符串
postgresql+psycopg://langchain:langchain@localhost:6024/langchain
# PGVector初始化参数
1. connection_string PostgreSQL 数据库连接字符串
2. collection_name 向量集合名称,用于在数据库中标识不同的向量集合
3. embedding_function 使用的嵌入模型
可选参数
4. pre_delete_collection 是否在创建新集合前删除同名集合
5. distance_strategy
- 可选值 :
- "cosine" :余弦相似度
- "euclidean" :欧几里得距离
- "manhattan" :曼哈顿距离
- 说明 :用于计算向量间距离的策略
PGVector 类提供了以下五个主要方法:
from_documents:从文档和嵌入模型创建一个向量存储实例。
similarity_search:在向量存储中执行相似性搜索,返回与查询最相似的前 k 个文档。
add_documents:向向量存储中添加新文档。
delete:从向量存储中删除文档。
get_by_ids:根据文档 ID 获取文档。
# 需要安装langchain_postgres
from langchain_community.document_loaders import TextLoader
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_postgres.vectorstores import PGVector
from langchain_core.documents import Document
import uuid
# 请参阅上面的docker命令以启动启用了pgvector的PostgreSQL实例。
# 用于连接启用了pgvector的PostgreSQL数据库的连接字符串
connection = "postgresql+psycopg://langchain:langchain@localhost:6024/langchain"
# 加载文档并将其拆分为多个小块
# 从指定路径以UTF-8编码加载原始文档
raw_documents = TextLoader('./test/deeplearning.txt', encoding="utf-8").load()
# 初始化文本拆分器以将文档拆分成较小的块
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 每个块的最大大小
chunk_overlap=200 # 连续块之间的重叠部分
)
# 将原始文档拆分成较小的块
documents = text_splitter.split_documents(raw_documents)
# 为文档创建嵌入
# 使用预训练的HuggingFace模型初始化用于生成嵌入的模型
embeddings_model = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2" # 高效的语义模型
)
# 从文档列表生成一个向量存储。此存储将文档的嵌入向量保存在 PostgreSQL 数据库中,以便后续进行相似性搜索等操作。
db = PGVector.from_documents(
documents, # 要存储的文档
embeddings_model, # 用于生成嵌入的模型
connection=connection # PostgreSQL数据库的连接字符串
)
# 在向量存储(db)中找到最相似的文档
results = db.similarity_search("query", k=4) # 搜索与查询最相似的前4个文档
# 向 向量存储中生成新文档
# 为新文档生成唯一ID
ids = [str(uuid.uuid4()), str(uuid.uuid4())]
# 使用生成的ID向向量存储中添加新文档
db.add_documents(
[
Document(
page_content="池塘里有猫", # 文档的内容
metadata={"location": "池塘", "topic": "动物"}, # 与文档相关的元数据
),
Document(
page_content="池塘里也有鸭子", # 文档的内容
metadata={"location": "池塘", "topic": "动物"}, # 与文档相关的元数据
),
],
ids=ids, # 新文档的ID
)
# 打印文档添加的确认信息
print("文档添加成功。\n获取的文档数量:",
len(db.get_by_ids(ids))) # 获取并打印添加的文档数量
# 从向量存储中删除文档
print("删除ID为", ids[1], "的文档")
db.delete({"ids": ids}) # 删除具有指定ID的文档
# 打印文档删除的确认信息
print("文档删除成功。\n获取的文档数量:",
len(db.get_by_ids(ids))) # 获取并打印剩余的文档数量
SQLRecordManager用于追踪向量数据库中的文档索引状态。
文档索引追踪:记录哪些文档已被索引到向量数据库中,以及索引的时间
增量更新支持:识别哪些文档需要新增、更新或保持不变
文档去重:防止重复索引相同的文档
高效清理:提供多种清理模式,删除过时的文档向量
主要方法
create_schema()
:创建必要的数据库表结构
update()
:更新记录状态,记录文档索引时间
exists()
:检查文档是否已被索引
list_keys()
:列出符合条件的记录
delete_keys()
:删除指定记录
get_time()
:获取服务器时间戳
# 初始化SQL记录管理器
1. namespace :用于标识记录管理器的命名空间。命名空间可以帮助区分不同的记录集合,避免冲突。
2. db_url :数据库连接字符串,指定了连接到 PostgreSQL 数据库的详细信息。
- 格式 : postgresql+psycopg://username:password@host:port/database
- username :数据库用户名
- password :数据库密码
- host :数据库主机地址(如 localhost )
- port :数据库端口号(如 6024 )
- database :数据库名称(如 langchain )
SQLRecordManager 是 langchain 库中的一个类,用于管理数据库中的记录。它提供了创建、更新、删除和查询记录的功能。通过指定 namespace 和 db_url ,你可以初始化一个记录管理器来操作特定数据库中的记录。
# SQLRecordManager通常与index函数搭配使用
# index部分参数介绍
必需参数
1. docs :一个document对象
2. record_manager :SQLRecordManager 的实例
3. vectorstore :PGVector 的实例
可选参数
4. cleanup :指定如何处理重复文档。
- 可选值 :
- "incremental" :增量清理,防止重复添加相同的文档。
- "full" :完全清理,删除所有旧版本并添加新版本。
5. source_id_key :确保每个文档在数据库中都有唯一的标识。
# 需要安装chroma db
from langchain.indexes import index
from langchain_community.vectorstores import Chroma
from langchain.docstore.document import Document
from langchain_huggingface import HuggingFaceEmbeddings
from langchain.indexes import SQLRecordManager
# 初始化SQL记录管理器
record_manager = SQLRecordManager(
namespace="my_docs_namespace", # 命名空间,用于隔离不同索引集
db_url="postgresql+psycopg://langchain:langchain@localhost:6024/langchain" # 数据库连接URL
)
# 创建必要的数据库表结构
record_manager.create_schema()
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2" # 高效的语义模型
)
# 这个即将被移除 按照报错进行修改
vectorstore = Chroma(embedding_function=embeddings, collection_name="my_collection")
# 准备文档
docs = [
Document(page_content="这是第一个文档", metadata={"source": "doc1.txt"}),
Document(page_content="这是第二个文档", metadata={"source": "doc2.txt"}),
]
# 索引文档(增量模式)
result = index(
docs,
record_manager,
vectorstore,
cleanup="incremental", # 增量清理模式
source_id_key="source", # 使用source字段作为文档源标识
)
print(f"新增: {result['num_added']}, 更新: {result['num_updated']}, "
f"跳过: {result['num_skipped']}, 删除: {result['num_deleted']}")
# 输出 新增: 2, 更新: 0, 跳过: 0, 删除: 0
# 假设文档内容有变化
updated_docs = [
Document(page_content="这是修改后的第一个文档", metadata={"source": "doc1.txt"}),
Document(page_content="这是第二个文档", metadata={"source": "doc2.txt"}),
Document(page_content="这是新增的第三个文档", metadata={"source": "doc3.txt"}),
]
# 再次索引
result = index(
updated_docs,
record_manager,
vectorstore,
cleanup="incremental",
source_id_key="source",
)
print(f"新增: {result['num_added']}, 更新: {result['num_updated']}, "
f"跳过: {result['num_skipped']}, 删除: {result['num_deleted']}")
使用MultiVectorRetriever对索引进行了优化
from langchain_community.document_loaders import TextLoader
from langchain_ollama import ChatOllama
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_postgres.vectorstores import PGVector
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel
from langchain_core.runnables import RunnablePassthrough
from langchain_core.documents import Document
from langchain.retrievers.multi_vector import MultiVectorRetriever
from langchain.storage import InMemoryStore
import uuid
from langchain_huggingface import HuggingFaceEmbeddings
# 步骤一:使用大模型生成摘要
# PostgreSQL数据库的连接字符串
connection = "postgresql+psycopg://langchain:langchain@localhost:6024/langchain"
collection_name = "summaries"
# 初始化嵌入模型
embeddings_model = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2" # 高效的语义模型
)
# 加载文档
loader = TextLoader("./test/deeplearning.txt", encoding="utf-8")
docs = loader.load()
# 分割文档
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
chunks = splitter.split_documents(docs)
# 定义提示模板
prompt_text = "Summarize the following document:\n\n{doc}"
prompt = ChatPromptTemplate.from_template(prompt_text)
# 初始化Ollama模型
llm = ChatOllama(
model="deepseek-r1:32b",
)
# 定义摘要链
# {"doc": lambda x: x.page_content} 是一个字典
summarize_chain = {
"doc": lambda x: x.page_content
} | prompt | llm | StrOutputParser()
# 并行处理文档块进行摘要
# 同时指定了最大的并发数 {"max_concurrency": 5}
summaries = summarize_chain.batch(chunks, {"max_concurrency": 5})
print(summaries)
# 步骤二 vectorstore和store用于存储原始摘要及其嵌入
# 初始化向量存储
vectorstore = PGVector(
embeddings=embeddings_model,
collection_name=collection_name,
connection=connection,
use_jsonb=True,
)
# 初始化内存存储 用于临时数据存储
store = InMemoryStore()
# 定义"doc_id"作为关联两个存储系统的关键字段
id_key = "doc_id"
# 初始化多向量检索器
retriever = MultiVectorRetriever(
vectorstore=vectorstore,
docstore=store,
id_key=id_key,
)
# 为每个文档块生成唯一的ID
doc_ids = [str(uuid.uuid4()) for _ in chunks]
# 将摘要文本包装为Document对象,并在元数据中嵌入ID,后添加到向量数据库,进行向量化处理
summary_docs = [
Document(page_content=s, metadata={id_key: doc_ids[i]})
for i, s in enumerate(summaries)
]
retriever.vectorstore.add_documents(summary_docs)
# 将原始文档存储在内存存储中,并将其与摘要文档通过ID关联
retriever.docstore.mset(list(zip(doc_ids, chunks)))
# 从向量存储中检索与查询最相似的摘要文档
sub_docs = retriever.vectorstore.similarity_search(
"chapter on philosophy", k=2
)
print("sub docs: ", sub_docs[0].page_content)
print("length of sub docs:\n", len(sub_docs[0].page_content))
# 步骤三 查询检索相关的完整上下文文档
# 使用检索器进行查询,会返回较大的源文档块
retrieved_docs = retriever.invoke("chapter on philosophy")
print(retrieved_docs)
print("length of retrieved docs: ", len(retrieved_docs[0].page_content))
# 输入阶段:从文件系统读取文本文档
# 分块阶段:将大型文档分割成较小的块
# 摘要阶段:使用DeepSeek-R1:32b模型生成每个块的摘要
# 存储阶段:
# - 摘要通过嵌入模型向量化,存储在PostgreSQL数据库
# - 原始文档块保存在内存中
# - 两者通过UUID相互关联
# 检索阶段:
# - 使用语义相似度在向量数据库中查找相关摘要
# - 通过ID映射找到对应的原始文档块
# - 返回完整的原始文档作为最终结果
RAG系统的核心流程
编入索引:对外部数据源进行预处理,并将表示数据的嵌入存储在向量存储中,以便于检索。
检索:根据用户的查询检索存储在向量存储中的相关嵌入和数据。
生成:将原始提示与检索到的相关文档合成,作为发送给模型进行预测的最终提示。
from langchain_postgres.vectorstores import PGVector
from langchain_huggingface import HuggingFaceEmbeddings
# 数据库连接配置
connection = "postgresql+psycopg://langchain:langchain@localhost:6024/langchain"
# 初始化嵌入模型
embeddings_model = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2"
)
# 连接到现有的向量存储
db = PGVector(
connection=connection,
embeddings=embeddings_model,
collection_name="langchain" # 确保这是你之前使用的collection_name
)
# 检索操作
query = "你想要查询的内容"
# 基础相似度检索
# k=2 表示返回2个最相关的文档
# 直接返回相关文档内容和元数据
docs = db.similarity_search(query, k=2)
# 方法2:使用检索器 实现灵活配置
retriever = db.as_retriever(search_kwargs={"k": 2})
docs = retriever.invoke(query)
# 方法3:带分数的相似度搜索
# 除了返回相关文档外,还会返回相似度分数
# 适用于需要根据相似度分数进行筛选或排序的场景适用于需要根据相似度分数进行筛选或排序的场景
docs_and_scores = db.similarity_search_with_score(query, k=2)
# 输出查询结果
for doc in docs:
print("文档内容:", doc.page_content)
print("元数据:", doc.metadata)
# 删除操作
# 方法1:删除指定的文档
ids_to_delete = ["021ce213-54bf-44e2-aac1-f70db8bed151", "68bd0b6a-4146-4c8b-98b5-a410cd2ef1b6"] # 文档ID列表
db.delete(ids_to_delete)
# 方法2:删除整个集合
db.delete_collection()
# 方法3:条件删除(基于元数据)
filter = {"source": "specific_source"} # 根据元数据中的source字段筛选
db.delete(filter=filter)
# 添加操作
# 方法1:添加单个文档
doc = Document(
page_content="这是新添加的文档内容",
metadata={"source": "manual_input", "author": "user"}
)
db.add_documents([doc])
# 方法2:批量添加文档
docs = [
Document(page_content="文档1", metadata={"id": "1"}),
Document(page_content="文档2", metadata={"id": "2"}),
Document(page_content="文档3", metadata={"id": "3"})
]
db.add_documents(docs)
# 方法3:从文本直接添加
texts = ["这是第一段文本", "这是第二段文本", "这是第三段文本"]
metadatas = [
{"source": "text1"},
{"source": "text2"},
{"source": "text3"}
]
db.add_texts(texts, metadatas=metadatas)
# 待补充 使用as_retriever的各种灵活配置
# 效果不是很好 可能是因为输入的文本实在太大了
from langchain_community.document_loaders import TextLoader
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_postgres.vectorstores import PGVector
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import chain
connection = "postgresql+psycopg://langchain:langchain@localhost:6024/langchain"
# 加载数据并进行分块
raw_documents = TextLoader('./test/劳动法.txt', encoding='utf-8').load()
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, chunk_overlap=200)
documents = text_splitter.split_documents(raw_documents)
# 生成嵌入模型
embeddings_model = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2" # 高效的语义模型
)
db = PGVector.from_documents(
documents, embeddings_model, connection=connection)
# 创建检索器以检索2个相关文档
retriever = db.as_retriever(search_kwargs={"k": 2})
query = '劳动者的收入可以低于当地的最低工资吗?'
# 获取相关文档
docs = retriever.invoke(query)
print("输出相关文档")
print(docs)
print("输出检索后的第一部分:")
print(docs[0].page_content)
prompt = ChatPromptTemplate.from_template(
"""根据以下内容回答问题:{context} 问题:{question} """
)
llm = ChatOllama(model="deepseek-r1:32b")
llm_chain = prompt | llm
# 基于相关文档回答问题
result = llm_chain.invoke({"context": docs, "question": query})
print("最后输出的结果是:")
print(result)
# 再次运行,但这次封装逻辑以提高效率
# @chain 装饰器将此函数转换为LangChain可运行对象,
# 使其与LangChain的链操作和管道兼容
print("再次运行,但这次封装逻辑以提高效率\n")
@chain
def qa(input):
# 获取相关文档
docs = retriever.invoke(input)
# 格式化提示
formatted = prompt.invoke({"context": docs, "question": input})
# 生成答案
answer = llm.invoke(formatted)
return answer
# 运行
result = qa.invoke(query)
print(result.content)
RAG过于依赖于用户查询的质量,无法生成准确的输出。在生产环境中,用户可能以不完整、含糊不清或措辞不佳的方式构建查询,从而导致模型幻觉。
from langchain_core.prompts import ChatPromptTemplate
from langchain_ollama import ChatOllama
# 查询以无关信息开头,然后才问相关问题
query = '今天我起床刷牙,然后坐下来阅读新闻。然后我忘记了炉子上的食物。古希腊哲学史上的一些重要人物是谁?'
# 重写查询以提高准确性
rewrite_prompt = ChatPromptTemplate.from_template(
""" 我将输入一段文字 里面是用于的提问 请删去其中无用的描述 仅输出最简介的问题 内容:{x}"""
)
def parse_rewriter_output(message):
return message.content.strip('"').strip("**")
rewriter = rewrite_prompt | ChatOllama(model="deepseek-r1:32b") | parse_rewriter_output
new_query = rewriter.invoke({"x": query})
print("重写后的查询:", new_query)
# 去除思考过程后 输出:古希腊哲学史上的一些重要人物是谁?
# 将这个作为RAG大模型的输入提示词
多查询检索策略指示大型语言模型根据用户的初始查询生成多个查询,对数据源中的每个查询执行并行检索,然后将检索到的结果插入提示上下文,以生成最终的模型输出。此策略对于单个问题可能依赖于多个透视图来提供答案的用例特别有用。