LangChain入门(五)

LangChain实战入门(五):项目篇——构建企业级AI应用系统 在本篇中,我们将完整开发一个企业智能知识管理系统,它不仅能处理各种格式的文档,还能理解复杂查询、维护对话上下文,并通过智能工具调用解决实际问题。 阅读详情

本文主要讲解LangChain的核心组件(输出解析器,文档加载器,文本分割器,文本向量,检索器)。

1.输出解析器

负责获取模型的输出,并将输出转换为结构化的格式。他们将LLM的非结构话文本输出转换为结构化格式。这使得LLM的交互从模糊的文本对话变成了精确的数据API调用。

1.1与with_structured_output()的区别

维度不同:输出解析器是一个功能性组件,而with_structured_output()是聊天模型的一个方法。

用法不同:输出解析器的工作流程可以是链式的。而with_structured_output()不行,只能手动调用,返回一个新的具备了结构化输出能力的Runnable

1.2使用示例:

from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

model=ChatOpenAI(model="gpt-4o-mini")
chain=model|StrOutputParser()
for chunk in chain.stream("写⼀⾸夏天的诗词,50字以内。"):
    print(chunk,end="|")

1.3解析结构化对象输出

要结构化对象,需要用到的输出解析器是PydanticOutputParser。

其参数如下:

pydantic_object:要解析的pydantic模型。

内置方法:

        invoke():将单个输入转化为输出。

        get_format_instructions()->str:重要!

        作用:生成一个指令字符串这个字符串会被添加到发给LLM的提示的末尾。

        目的:告诉LLM应该以什么样的格式返回他的响应。

代码示例如下:

from typing import Optional

from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel,Field

model=ChatOpenAI(model="gpt-4o-mini")
class Joke(BaseModel):
    """给用户讲一个笑话"""
    setup:str=Field(description="这个笑话的开头")
    punchline:str=Field(description="这个笑话的妙语")
    rating: Optional[int]=Field(
        default=None,description="从1到10分,给这个笑话评分"
    )
#设置解析器
parser=PydanticOutputParser(pydantic_object=Joke)
#提示词模板
prompt=PromptTemplate(
    template="Answer the user query.\n{format_instructions}\n{query}\n",
    input_variables=["query"],
#   #partial_variables:提⽰模板携带的部分变量的字典,⽆需在每次调⽤提⽰时都传⼊它们。
#   #类型为 Mapping[str, Any],传⼊template携带的部分变量的字典。
    partial_variables={"format_instructions":parser.get_format_instructions()},
)
chain=prompt|model|parser
for chunk in chain.stream({"query":"给我讲一个关于唱歌的笑话"}):
    print(chunk,end="|")

1.4解析JSON输出

要输出JSON格式,需要用到的输出解析器是JsonOutParser。其参数如下:

pydantic_object:用于验证Pydantic对象。如果为空则不执行任何验证。

内置方法:

        invoke():将单个输入转换为输出。

        get_format_instructions()->str:重要!

作用:生成一个指令字符串,这个字符串会被添加到发送给LLM的提示的末尾。

目的:告诉LLM应该以什么样的格式返回他的响应。

代码如下:

from typing import Optional

from langchain_core.output_parsers import PydanticOutputParser, JsonOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel,Field

model=ChatOpenAI(model="gpt-4o-mini")
class Joke(BaseModel):
    """给用户讲一个笑话"""
    setup:str=Field(description="这个笑话的开头")
    punchline:str=Field(description="这个笑话的妙语")
    rating: Optional[int]=Field(
        default=None,description="从1到10分,给这个笑话评分"
    )
#设置解析器
parser=JsonOutputParser()
#提示词模板
prompt=PromptTemplate(
    template="Answer the user query.\n{format_instructions}\n{query}\n",
    input_variables=["query"],
#   #partial_variables:提⽰模板携带的部分变量的字典,⽆需在每次调⽤提⽰时都传⼊它们。
#   #类型为 Mapping[str, Any],传⼊template携带的部分变量的字典。
    partial_variables={"format_instructions":parser.get_format_instructions()},
)
chain=prompt|model|parser
print(chain.invoke({"query":"给我讲⼀个关于唱歌的笑话"}))

2.文档加载器

2.1RAG介绍

对于AI大模型它最擅长的是语义理解和文本总结,最不擅长的是提取实时信息。

对于搜索引擎来说,他最擅长的是获取实时信息,缺点是信息分散,每次都需要人为进行总结。

因此大模型和搜索引擎的结合,就是给AI配备了一个活字典,让AI可以随时进行查阅。

搜索引擎可以帮助我们解决实时数据的获取,但获取到的数据也是受限的。它只能获取到公开在网络中的数据而无法获取到一些本地数据,或企业内部的私有数据等。

解决办法是:使用RAG技术。当用户向LLM提问的时候,系统首先在知识库中进行语义搜索找到最相关的内容,然后将这些内容核问题一起交给LLM来生成答案。

2.2RAG流程

RAG流程分为离线数据处理和在线检索两个过程。

RAG知识库可以是本地文档,公司内部文档等一些私有化数据。但这些私有数据或文档实际上并不能很好地被直接进行检索访问。需要将这些私有化数据建成可以被检索的知识库,这就是离线数据处理要做的事情。而在线检索则是我们依赖知识库查询通过大模型生成结果的过程。

首先第一步是文档加载:加载多种不同来源加载文档,LangChain提供了100多种不同的文档加载器。

第二步是文本分割:他是把Documents切分为指定大小的块。

第三步是存储:它涉及到两个环节。分别是将切分好的文档块进行嵌入,即将文档块转换为向量的形式。同时将嵌入后的向量数据,存储到数据库中。

第四步是检索:数据存入向量数据库后。当我们需要进行数据检索的时候,会通过某种检索算法找到与输入问题相似的文档块。

第五步是输出:把问题以及检索出来的文档快一起交给LLM,它会通过问题和检索出来的提示一起生成更加合理的答案。

2.3Doucment文档类

要想实现RAG,首先就需要从源中获取到数据,即加载数据或文档。这是通过LangChain的文档加载器完成的。LangChain文档加载器可以将各种数据源加载成一系列的文档对象Document。class langchain_core.documents.base.Document用于存储一段文本和相关元数据的类,我们可以直接定义LangChain文档列表,如下所示:

from langchain_core.documents import Document
documents = [
    # 单个Document对象通常表⽰较⼤⽂档的⼀个块
    Document(
        # 内容字符串
        page_content="狗是很好的伴侣。",
        # 元数据字典
        # 元数据属性可以捕获有关⽂档源、与其他⽂档的关系以及其他信息的信息。
        metadata={"source": "mammal-pets-doc"},
    ),
    Document(
        page_content="猫是独⽴的宠物。",
        metadata={"source": "mammal-pets-doc"},
    )
]

这里我们定义了一个documents文档列表,其内包含了两个Document文档对象。通常单个Document对象表示较大文档的一个块/页。每个Document对象包含了以下参数:

id。可选的文档标识符。理想情况下,这应该在整个文档集合中是唯一的,并格式化为UUID。

page_count:字符串文本。

metadata:与内容关联的任意元数据。类型为dict [Optional]。

2.4加载PDF文档

将本地的PDF加载到LangChain中其实就是将PDF文档转换为一个个Document对象。这时候就需要我们使用PyPDFLoader文档加载器完成这一个功能。PyPDFLoader类有以下关键函数:

init()初始化函数,入参file_path,表示要加载的PDF文档路径。

load()->list[Document]:将数据加载到文档对象中,返回文档对象列表。

现在让我们加载一个本地PDF文档看下效果。

from langchain_community.document_loaders import PyPDFLoader
loader=PyPDFLoader(file_path="./docs/pdf/26王道《操作系统》.pdf")
docs=loader.load()
print(f"PDF文档总页数为:{len(docs)}\n")
print(f"第一页的文本内容是:{docs[0].page_content[:200]}\n")
print(f"第一页的元数据字典是:{docs[0].metadata}")

2.5加载Markdown文件

将本地的Markdown文档加载到LangChain中需要我们使用UnstructuredMarkdownLoader文档加载器完成这一个功能,他又以下关键函数:

 init()初始化函数,所需参数:file_path:表示路径,mode:加载文档所使用的模式,可以是single或者elements。默认为single。

load()->list[Document]:将数据加载到文档对象中,返回文档对象列表。

from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter

loader=UnstructuredMarkdownLoader("./docs/markdown/Linux线程.md")
data=loader.load()
text_splitter=CharacterTextSplitter(
    separator="\n\n ",
    chunk_size=400,
    chunk_overlap=50,
    length_function=len,
    is_separator_regex=False,
)
documents=text_splitter.split_documents(data)
for document in documents[:10]:
    print("*"*30)
    print(document)

3.文档分割器

我们已经知道通过文档加载器完成各种数据源的加载将其转换为数据源的加载,将其转换为文档对象Document。那么接下来要做的是文档拆分。

文档拆分是将大的文本分解为更小的易于管理的块。这对于索引数据并将其传递到模型中都很有用。因此大块更难搜索并且不适合模型的有限上下文窗口。拆分可以提高搜索结果的粒度,从而更精确的将查询与相关文档部分进行匹配。

我们可以根据文档的长度进行拆分:基于字符长度拆分和基于token长度拆分。

3.1基于字符长度来拆分

还是上面的例子:

from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter

loader=UnstructuredMarkdownLoader("./docs/markdown/Linux线程.md")
data=loader.load()
text_splitter=CharacterTextSplitter(
    separator="\n\n ",
    chunk_size=400,
    chunk_overlap=50,
    length_function=len,
    is_separator_regex=False,
)
documents=text_splitter.split_documents(data)
for document in documents[:10]:
    print("*"*30)
    print(document)

其中separator代表选择分隔符。chunk_size代表目标块的大小。chunk_overlap代表块之间的重叠大小。length_function代表使用测量长度的函数。is_separator_regex代表是否为正则表达式。

3.2基于Token长度进行拆分

之前我们讲过LLM大模型并不是直接接收字符串,而是需要现做token切分编码。这里我们可以借助tiktoken分词器,来进行token的切分编码。

import tiktoken
# 定于cl100k_base编码⽅式的分词器
enc = tiktoken.get_encoding("cl100k_base")
# 进⾏切分编码
enc_output = enc.encode("my name is LiHua!")
# 打印结果
print(f"编码后的token:{str(enc_output)}")
for token in enc_output:
    print(f"将token: {str(token)} 变成⽂本:{str(enc.decode_single_token_bytes(token))}")

在LangChain中我们可以使用from_tiktoken_encoder()方法来定义根据tiktoken分词器拆分文本

text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base", chunk_size=200, chunk_overlap=50
)

4.文本向量

4.1嵌入模型

嵌入的核心思想是将人类世界的符号转换为计算机能够理解的数值形式,并且要求这种转换能够保留原始的符号语义和关系。它是把人类语言翻译成计算机的数学语言。

嵌入模型是表示型模型。它的目的不是生成文本,而是为输入的文本创建一个最佳的富含语义的数值表示。

4.2嵌入式模型应用场景:

1.语义搜索

语义搜索时将查询和文档库中的所有文档转换为向量,然后计算查询向量与所有文档向量的相似度,返回最相似的文档。借助嵌⼊模型进⾏⽂档搜索的过程:1)为多文档生成其各自的向量。2)为搜索查询语句生成向量。3)衡量查询向量与每个文档向量之间的相似性,得到相似度最高的文档。

2.检索增强生成

当用户向LLM提问时,系统首先使用嵌入模型在知识库中进行语义搜索,找到最相关的内容,然后将这些内容和问题一起交给LLM来生成答案。

4.3Embeddings嵌入模型类

1.定义嵌入模型

from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(
model="text-embedding-3-large", # 是 OpenAI 2024年发布的最新嵌⼊模型,⽣成3072维
的⾼质量向量
)

他其中设计了两个核心方法来处理文本嵌入。

.embed_documents():用于处理文档。它的输入是多个文本,例如将一个知识库里的所有段落都转换为向量后存入数据库就会使用这个方法。

它返回一个二维列表。外层列别哦啊的每个元素对应一个输入文档,内层则是该文档的向量表示。

.embed_query():用于处理查询query。它的输入是单个文本。例如当用户提出一个问题时,需要将这个问题转换为向量,以便在数据库中搜索相似的文档段落,就会使用这个方法。

它返回一个一维列表,里面是浮点数,代表单个查询文本的向量。

之所以设计成两个方法是因为某些嵌入模型提供商会针对被搜索的文档和搜索查询本身采用不同的优化策略和模型。即使底层是同一个模型,也可能对两者进行不同的预处理,以获得更好的搜索效果。

2.嵌入文档列表

embed_documents的语义是索引。它的目的是预处理大量文本,为他们创建向量表示,以便后续被搜索。这一般是一个离线批量处理的过程,代码如下:

from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import CharacterTextSplitter

markdown_path="./docs/markdown/Linux线程.md"
#single模式加载后默认只有一个Document对象
loader=UnstructuredMarkdownLoader(markdown_path)
data=loader.load()
#生成分割器
text_splitter=CharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="cl100k_base",
    chunk_size=200,
    chunk_overlap=50
)
#分割文档
documents=text_splitter.split_documents(data)
#定义嵌入模型
embeddings=OpenAIEmbeddings(model="text-embedding-3-large")
#嵌入文档列表,生成向量列表
texts=[doc.page_content for doc in documents]
documents_vector=embeddings.embed_documents(texts)
print(f"文档数量为:{len(documents)},生成了{len(documents_vector)}个向量列表")
print(f"第一个文档向量维度:{len(documents_vector[0])}")
print(f"第二个文档向量维度:{len(documents_vector[1])}")

3.嵌入单个查询

embed_query的语义是"搜索"。它的目的是在用户发起请求时,实时地将一个问题或者指令转换为向量,用于在索引的文档向量中检索,这是一个在线,实时,按需处理的过程。

为单个查询生成向量的代码如下:

from langchain_openai import OpenAIEmbeddings
#定义嵌入模型
embeddings=OpenAIEmbeddings(model="text-embeddings-3-large")
query_vector=embeddings.embed_query("在项目中遇到了哪些挑战?如何解决?”)
print(f"向量维度:{len(query_vector)}")
print(f"向量前五个数值为:{query_vector[:5]")

4.4向量存储

在LangChain中实际并不需要我们直接手动调用嵌入模型生成向量然后手动比较向量,实际上向量是被管理在专门的向量存储介质中,如向量数据库。向量存储的核心任务是解决一个传统数据库不擅长的问题:基于内容的相似性搜索,而不是基于精确匹配的查询。向量数据库提供了专门用于高效存储管理和检索高维向量的能力。其核心是:高效的组织和检索这些数据。

常见的向量数据库核心机制如下:

1.专门的索引。

这是向量数据库的灵魂。它们不会使用暴力搜索而是预先为向量构建一种特殊的索引结构。

常见的方法有近似最近邻搜索:未来追求极致的速度,它愿意牺牲一点点的精度。它通过聚类分层压缩等算法技术,将搜索范围从整个数据库缩小到几个最可能的候选集。

2.向量相似度计算优化。

向量数据库底层使用高度优化的库来进行向量计算,如FAISS向量数据库,它能快速处理大规模数据并支持在高维空间进行相似性的搜索。

这些库充分利用了CPU的SIMD指令集和GPU的并行计算的能力,让大规模的向量计算速度极快。

我们可以利用专门的向量数据库,通过构建索引,优化计算流程并提供丰富的过滤和管理功能,从而对海量高维的向量数据进行快速可扩展的相似性检索的一套完整技术方案。

4.5内存存储

LangChain中的大多数向量存储时接收嵌入模型作为参数。如下表示:

from langchain_openai import OpenAIEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore
# 定义嵌⼊模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
# 内存存储初始化
vector_store = InMemoryVectorStore(embedding=embeddings)

添加文档

我们可以使用add_documents方法,向内存存储中添加文档。要注意的是该方法为添加的文档编排索引,索引列表随着该方法返回。

当我们相对某个文本进行数据检索的时候的两个步骤:

1.编制索引:用于从源中摄取数据并为其编排索引。

2.检索与生成:接收用户查询并从索引中检索相关数据,然后将其传递给模型。

其中第一步我们通过下面代码完成:

from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter

from TEST45 import vector_store

#生成分割器
text_splitter=CharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="cl100k_base",
    chunk_size=200,
    chunk_overlap=50
)
#加载文档,输出成 LangChain 统一的 Document 对象
data=UnstructuredMarkdownLoader(
    "./docs/markdown/Linux线程.md",
).load()
#分割文档
documents=text_splitter.split_documents(data)
#添加文档
ids=vector_store.add_documents(documents=documents)
print(f"共编排了{len(ids)}个文档索引")
print(f"前三个文档索引是:{ids[:3]}")

获取文档

使用get_by_ids的方法,通过索引列表获取对应的文档列表。如下所示:

from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter

from TEST45 import vector_store

#生成分割器
text_splitter=CharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="cl100k_base",
    chunk_size=200,
    chunk_overlap=50
)
#加载文档,输出成 LangChain 统一的 Document 对象
data=UnstructuredMarkdownLoader(
    "./docs/markdown/Linux线程.md",
).load()
#分割文档
documents=text_splitter.split_documents(data)
#添加文档
ids=vector_store.add_documents(documents=documents)
doc3=vector_store.get_by_ids(ids[:3])
#把 doc3 里前 3 个文档的内容,拿出来组成列表,再打印出来
print(f"{[doc.page_content for doc in doc3]}")
#print(f"共编排了{len(ids)}个文档索引")
#print(f"前三个文档索引是:{ids[:3]}")

4.6相似性搜索

想要获取根据相似性搜索的结果即嵌入单个查询,并查找相似的文档并将他们作为文档列表返回。这可以使用similarity_search方法来实现。代码如下:

from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter

from TEST45 import vector_store

#生成分割器
text_splitter=CharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="cl100k_base",
    chunk_size=500,
    chunk_overlap=50
)
#加载文档,输出成 LangChain 统一的 Document 对象
data=UnstructuredMarkdownLoader(
    "./docs/markdown/Linux线程.md",
).load()
#分割文档
documents=text_splitter.split_documents(data)
#添加文档
ids=vector_store.add_documents(documents=documents)
doc3=vector_store.get_by_ids(ids[:3])
#把 doc3 里前 3 个文档的内容,拿出来组成列表,再打印出来
#print(f"{[doc.page_content for doc in doc3]}")
#print(f"共编排了{len(ids)}个文档索引")
#print(f"前三个文档索引是:{ids[:3]}")
search_docs = vector_store.similarity_search(query="Linux如何查看线程信息?", k=1)
for doc in search_docs:
    print("*" * 30)
    print(doc.page_content)

那么这时候我们就有疑问,为什么不直接是问题的答案而只是相似的文档呢?

此时我们可以将最相关内容和我们提出的问题一起交给LLM生成答案,这便能极大地提高答案的准确性。以上流程叫做检索增强生成(RAG)这是当前大语言模型应用的和新模式。

虽然向量数据库实现了搜索算法,来有效地搜索所有嵌入的文档以找到最相似的文档。但现实场景中,我们还希望通过先根据元数据进行过滤,来帮助缩小搜索范围。例如从特定来源或日期范围检索文档。

这次,我们给搜索方法加入了filter参数,它接受一个bool的值,表示我们可以根据条件选择是否过滤某些文档,再去进行搜索。

4.7元数据过滤

每个Document对象,都包含了以下参数:

id:可选的文档标识符。理想情况下这应该是整个文档集合中是唯一的。

page_contetnt:字符串文本

metadata:与内容关联的任意元数据。类型为dict[Optional]

虽然向量数据库实现了搜索算法,来有效地搜索所有嵌入地文档以找到最相似的文档。但现实场景中我们还是希望先根据元数据进行过滤来帮助缩小搜索范围。

from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter
from langchain_core .documents import Document
from TEST45 import vector_store

#生成分割器
text_splitter=CharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="cl100k_base",
    chunk_size=500,
    chunk_overlap=50
)
#加载文档,输出成 LangChain 统一的 Document 对象
data=UnstructuredMarkdownLoader(
    "./docs/markdown/Linux线程.md",
).load()
#分割文档
documents=text_splitter.split_documents(data)
#添加文档
ids=vector_store.add_documents(documents=documents)
doc3=vector_store.get_by_ids(ids[:3])
#把 doc3 里前 3 个文档的内容,拿出来组成列表,再打印出来
#print(f"{[doc.page_content for doc in doc3]}")
#print(f"共编排了{len(ids)}个文档索引")
#print(f"前三个文档索引是:{ids[:3]}")
def _filter_function(doc:Document)->bool:
    return doc.metadate.get("source")=="hahaha"
search_docs = vector_store.similarity_search(query="Linux如何查看线程信息?", k=1,filter=_filter_function)
for doc in search_docs:
    print("*" * 30)
    print(doc.page_content)

4.8Redis向量存储

我们还可以使用Redis来存储向量。从本质上来讲,Redis是一种键值型的NoSQL数据库,除了传统用例以外。它还提供了诸如搜索和查询功能等额外能力,允许用户在Redis内创建二级索引结构。

4.8.1基本概念

RediSearch是Redis官方提供的一款高性能搜索与全文索引引擎模块。它基于Redis构建,使用户能够直接在Redis数据库中执行复杂的搜索和分词查询,无需引入外部搜索引擎。

Index是RediSearch模块的概念用于定义一个查询目录。它是一个独立的数据结构建立在多个Redis Keys(Hash类型)之上,这专门为了激素执行文本搜索过滤和聚合而设计。

Index Fields是在创建索引时,明确指定的那些需要被索引的字段。metadata schema则是用来描述元数据的结构声明。

4.8.2环境设置

使用Redis来存储向量,首先需要将相关环境配置好。

第一步,启动Redis服务端:使用Docker启动Redis实例。

使用:

docker run -d -p 6379:6379 -it redis:latest

第二步,安装Redis客户端包,以便将来定义客户端,以及运行搜索和查询命令。

pip install redis

第三步,在LangChain中想要使用Redis向量库,需要安装langchain-redis包

pip install -qU langchain-redis

第四步,定义Redis连接URL,客户端连接Redis需要使用

第五步,测试连接

import redis
redis_url="redis://localhost:6379"
redis_client=redis.from_url(redis_url)
print(redis_client.ping())

4.8.3基本操作

LangChain中使用RedisVectorStore 初始化Redis向量存储。由于Redis需要相关配置因此LangChain提供了RedisConfig配置类供我们使用。如下所示:

from langchain_openai import OpenAIEmbeddings
from langchain_redis import RedisConfig, RedisVectorStore
#嵌入模型
embeddings=OpenAIEmbeddings(model="text-embedding-3-large")
#配置Redis客户端
redis_url="redis://localhost:6379"
#config = 给 Redis 向量库的 “规则说明书”
config=RedisConfig(
    index_name="qa",
    redis_url=redis_url,
    metadata_schema=[
        {"name": "category", "type": "tag"},
        {"name": "num", "type": "numeric"},
    ],
)
#Redis存储初始化
vector_store=RedisVectorStore(embeddings,config=config)

RedisVectorStore类其初始化参数如下:embeddings:用于存储的Embeddings实例。config:可选的RedisConfig对象。

RedisConfig配置类其参数如下:index_name:索引的名字。key_prefix:Redis Key的前缀。redis_url:Redis实例的URL。metadata_schema:元数据字段的schema

我们可以根据Index Name查询其下的所有的Index Fields,使用rvl命令行工具来检查索引。

添加文档:

我们可以使用add_documents方法,向向量库中去添加文档。这次我们可以给被分割的文档添加相关的元数据。如下所示:

from langchain_openai import OpenAIEmbeddings
from langchain_redis import RedisConfig, RedisVectorStore
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import CharacterTextSplitter

#嵌入模型
embeddings=OpenAIEmbeddings(model="text-embedding-3-large")
#配置Redis客户端
redis_url="redis://localhost:6379"
#config = 给 Redis 向量库的 “规则说明书”
config=RedisConfig(
    index_name="qa",
    redis_url=redis_url,
    metadata_schema=[
        {"name": "category", "type": "tag"},
        {"name": "num", "type": "numeric"},
    ],
)
#Redis存储初始化
vector_store=RedisVectorStore(embeddings,config=config)
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base", chunk_size=200, chunk_overlap=50
)
# 加载⽂档
data = UnstructuredMarkdownLoader("./docs/markdown/Linux线程.md", category="QA").load()
# 分割⽂档
documents = text_splitter.split_documents(data)
# 为⽂档添加元数据
for i, doc in enumerate(documents, start=1):
    doc.metadata["category"] = "QA"
    doc.metadata["num"] = i
ids = vector_store.add_documents(documents=documents)
print(f"共编排了{len(ids)}个⽂档索引")
print(f"前3个⽂档的索引是:{ids[:3]}")

获取文档:

使用get_by_ids方法,通过索引列表获取对应的文档列表。如下所示:


ids="***"
doc3=vector_store.get_by_ids(ids)
print(f"{[doc.page_content for doc in doc3]}")

4.8.4向量搜索

1.相似性搜索,是哦那个similarity_search的方法实现。代码如下:

search_docs =vector_store .similarity_search(query="什么是线程?", k=2)
  for doc in search_docs :
      print("*" *30)
      print(doc.page_content)

2.元数据过滤

在他们搜索之前都可以根据元数据进行过滤。示例如下:

from redisvl.query.filter import Tag

# 正确过滤:只搜 category = "QA" 的文档
filter_condition = Tag("category") == "QA"

# 带过滤的检索
scored_results = vector_store.similarity_search_with_score(
    query="数据库表怎么设计的?",
    k=2,
    filter=filter_condition
)

4.8.5最大边际相关性检索

最大边际相关性是一种重新排序算法,它使用语义相似性为基础工具,从一个候选集中挑选出一组既能代表查询主题又彼此多样化的结果。

使用MMR搜索,需要使用max_marginal_relevance_search方法。代码如下:

from redisvl.query.filter import Tag,Num
category_is_qa=Tag("category")=="qa"
mmr_results=vector_store.max_marginal_relevance_search(
    query="怎么设计的",
    k=2,
    fetch_k=10,
    filter=filter_condition
)
for doc in mmr_results:
    print(f"Content:{DOC.page_content[:100]...")
    print(f"Metadata:{doc.metadata}")

5.检索器

5.1检索系统

检索系统是一个为了满足用户信息需求,从大规模非结构化的数据集合中,自动高效的查找排序并返回相关信息的计算机系统。

它的任务是:在正确的时间以正确的方式将信息传递给正确的人。

5.2检索器

检索器是检索系统中的一个核心组件,它接收来自用户接口的查询,检索出包含查询关键词的文档集合。

5.3使用向量数据库作为检索器

向量存储是索引和检索非结构化数据的一种强大而有效的方法,可以通过调用向量数据库的as_retriever方法,将向量存储用作检索器,在这里我们使用Redis向量存储。

from langchain_openai import OpenAIEmbeddings
from langchain_redis import RedisConfig,RedisVectorStore
embeddings=OpenAIEmbeddings(model="text-embedding-3-large")
redis_url="redis://localhost:6379"
config=RedisConfig(
    index_name="qa",
    redis_url=redis_url,
    metadata_schema=[
        {"name":"category","type":"tag"},
        {"name":"num","type":"numeric"},
    ],
)
#Redis存储初始化
vector_store=RedisVectorStore(embeddings,config=config)
retriever=vector_store.as_retriever()
docs=retriever.invoke("数据库是怎么写的")
for doc in docs:
    print("*"*30)
    print(doc.page_content[:30])

as_retriever方法也支持我们传递相关参数修改搜索结果。如:

search_type:设置相似算法

searcg_kwargs:

        k:限制检索器返回的文档K的数量。

        fetch_k:要传递给MMR算法的文档量。

要注意的是他虽然是Runnable对象,但其不提供任何流式处理,因为它本身通常是同步的,阻塞的操作。

另外除了使用as_retriever方法外,我们还可以自行创建一个检索器。

检索器特点:LangChain检索器是一个Runnable的对象;LangChain检索器输入为查询字符串,输出为文档列表。综上我们可以:

from langchain_core .runnables import chain
from typing import List
from langchain_core .documents import Document
@chain
def retriever(query : str) -> List[Document]:
return vector_store .similarity_search(query , k=2)
 docs = retriever .invoke("数据库表怎么设计的?")
 for doc in docs :
    print("*" *30)
    print(doc.page_content[:30])

上面定义了一个函数,使用@chain修饰,该修饰可以使其成为Runnable函数,且满足检索器输入输出的要求。在函数中,我们依旧使用向量数据库的相似性搜索方法,这样灵活性也更高想要进行元数据筛选也更方便。

6.RAG案例

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_redis import RedisConfig, RedisVectorStore

model=ChatOpenAI(model="gpt-4o-mini")
embeddings=OpenAIEmbeddings(model="text-embedding-3-large")
redis_url="redis://localhost:6379"
config=RedisConfig(
    index_name="qa",
    redis_url=redis_url,
    metadata_schema=[
        {"name":"category","type":"tag"},
        {"name":"num","type":"numeric"},
    ],
)
vector_store=RedisVectorStore(embeddings,config=config)
retriever=vector_store.as_retriever()
#定义提示词模板
prompt=ChatPromptTemplate.from_messages(
    [
        (
            "human",
            """你是负责问题的助手。使用以下检索到的片段回答问题。如果你不知道答案你就说不知道,
            最多用三句话。
            Question:{question}
            Context:{context}
            Answer: """,
        ),
    ]
)
#将文档转换为字符串
def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)
rag_chain=(
    {"context":retriever|format_docs,"question":RunnablePassthrough()}
    |prompt
    |model
    |StrOutputParser()
)
#执行链,流式输出
for chunk in rag_chain.stream("怎么编写线程?"):
    print(chunk,end="|",flush=True)

【五.LangChain技术与应用】【8.LangChain提示词模板基础:从入门到精通】 早上八点,你端着咖啡打开IDE,老板刚甩来需求:“做个能自动生成产品描述的AI工具”。你自信满满地打开ChatGPT的API文档,结果半小时后对着满屏的"输出结果不稳定"、"格式总出错"抓耳挠腮——这时候你真需要好好认识下LangChain里的提示词模板了。 阅读详情

相关推荐

FTViewSE利用VB脚本制作简易日报表

罗克韦尔SE用VB做的简单的报表

z0800656的博客 3018

LangChain 83 LangGraph 从入门到精通五

这个接口和 StateGraph 一样,唯一的区别是它不会随着时间更新状态对象,而是依赖于在每一步传递完整的状态。这意味着从一个节点返回的任何东西都是下一个节点的输入。

AI架构师易筋 1993

LCD工程师必看:Pixelclock、Mipi Clk计算全解析(附实用公式)

本文为LCD工程师详细解析了像素时钟(Pixelclock)与MIPI时钟(Mipi Clk)的核心计算方法。文章从时序参数构成入手,通过实战公式推导,系统阐述了从屏幕分辨率、刷新率到最终MIPI速率的高效计算链路,并提供了实用的调试思路与排查清单,帮助工程师快速解决画面撕裂、闪烁等常见问题。

weixin_29266679的博客 377

langchain入门系列之五 初探代理

代理的核心思想是使用LLM来选择要采取的一系列动作。在链式结构中,一系列动作是硬编码的(在代码中)。在代理中,使用语言模型作为推理引擎来确定要采取的动作及其顺序。

笑笑布丁的博客 688

芯片设计新手必看:5分钟搞懂工艺角(Process Corner)到底是什么,为啥仿真总要用它?

本文详细解析了芯片设计中的工艺角(Process Corner)概念及其在仿真中的重要性。通过类比马拉松运动员的性能差异,解释了晶体管制造过程中的天然波动,并介绍了TT/FF/SS等工艺角代码的含义。文章还探讨了为何需要跑全工艺角仿真,以及现代芯片验证的完整流程,包括PVT矩阵和多模式分析(MMMC),帮助芯片设计新手快速掌握这一关键概念。

weixin_28713947的博客 237

【scp】Windows 下scp报错 bad permissions

Windows 下使用 SSH 连接报 Bad owner or permissions on C:\\Users\\Administrator/.ssh/config 错误问题解决 C:\Users\Administrator>scp -P 66666 D:\1111.pdf zzd@58.213.45.xx:/home/zzd/ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING:...

走在搬砖的路上! 4150

LangChain实战快速入门笔记(五)--LangChain使用之Tools

要构建更强大的AI工程应用,只有生成文本这样的“纸上谈兵”能力自然是不够的。工具Tools不仅仅是“肢体”的延伸,更是为“大脑”插上了想象力的“翅膀”。借助工具,才能让AI应用的能力真正具备无限的可能,才能从“认识世界”走向“改变世界Tools 用于扩展大语言模型(LLM)的能力,使其能够与外部系统、API 或自定义函数交互,从而完成仅靠文本生成无法实现的任务(如搜索、计算、数据库查询等)。•增强 LLM 的功能:让 LLM 突破纯文本生成的限制,执行实际操作(如调用搜索引擎、查询数据。

weixin_43665177的博客 1208

PCB_Project.PrjPcb 4 Not in version control

Altium Designer中PCB项目出现"NOT in version control"错误的解决方法:1. 禁用不需要的版本控制集成(DXP→Preferences→Version Control);2. 清理项目缓存(Projects面板右键→Clean Project);3. 检查文件读写权限;4. 如无效可新建项目重新添加文件。如需使用SVN版本控制,需正确配置SVN客户端路径,创建或连接SVN仓库,并将项目文件提交至版本控制系统。注意新建SVN仓库必须使用空目录,建议使用

whm128的博客 1580

LangChain入门⑤:从RAG实战到对话记忆

本文介绍了如何在LangChain中实现对话记忆功能,解决大语言模型无状态的问题。

i235Uranium的博客 525

华为OD机试真题 新系统 9月16号 【统计特殊数字】

华为OD新系统机试真题【统计特殊数字】,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。华为OD机试真题【统计特殊数字】。

qq_45776114的博客 743

ACS运动控制器编程 怎么学习

本文介绍了ACS运动控制器编程的完整学习路线,从零基础到实战应用。ACS是以色列高端多轴运动控制平台,适用于自动化设备、精密平台等项目。学习路径包括:1)安装官方软件SPiiPlusControlSuite;2)掌握轴、运动模式和指令三个核心概念;3)学习10个基础指令;4)编写首个运动程序;5)结合C#进行联合编程。进阶内容包括多轴同步、电子凸轮等工业应用,并推荐官方手册作为主要学习资源。通过7天的系统学习,即可掌握单轴运动控制,结合C#实现工业级设备开发。

StevenChen的博客 1273

真正通俗易懂的Langchain入门学习(五)

等基础模块组合而成,就像用乐高积木搭建城堡——现在你已拥有所有基础积木块,是时候创造你的AI作品了!回复美化Transform。:每个复杂的AI应用都是由。数据库查询Agent。描述性分析Agent。结合前三项技术,打造。

caridle的专栏 845

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

LangChain从入门到实践(五)——LCEL链式调用

LangChain其核心概念就是Chain。 Chain翻译成中文就是“链”。用于将多个组件(提示模板、model模型、记忆、工具等)连接起来,形成可复用的工作流,完成复杂的任务。

2301_81888214的博客 1074

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Langchain从入门到精通(五)——OutputParser 组件及使用技巧

在使用大语言模型的时候,无论是使用 LangChain,还是直接使用模型的 API,都会遇到大语言模型的输出解析问题. 模型输出是“自然语言”

2301_81940605的博客 1273

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

langchain从入门到精通(五)——OutputParser 组件及使用技巧

在使用大语言模型的时候,无论是使用 LangChain,还是直接使用模型的 API,都会遇到大语言模型的输出解析问题. 模型输出是“自然语言”在 LangChain 中预设了大量的输出解析器,可以从StrOutputParser:字符串输出解析器,将原有的内容完整输出。StrOutputParser 是 LangChain 中使用频率最高的输出解析器,使用也非常简单,将传入的文本原样返。简单拆解: 输出解析器 = 预设提示 + 解析功能。“weather”: “晴”,“city”: “北京”,

swpucwf的博客 1393

LangChain 入门系列五:Tools创建及错误处理

1、LangChain框架中工具时一个被@tool装饰器修饰的函数 2、在LangChain框架中提供三种核心的工具创建方式,帮助工具有更好的可读性和可维护性:遵循google风格、Pydantic 模型定义入参结构、json schema 3、介绍了model和agent调用工具的方式 4、介绍了工具调用异常处理方式,当工具调用出现异常时通过友好处理可以帮助Agent继续执行不会中断。

piaomiaoaa的博客 148

【可能是全网最丝滑的LangChain教程】五、快速入门Conversation Retrieval Chain

基于LangChain设计一个带历史对话的聊天机器人

Jeffray1991的博客 1458

LangChain4j入门AI(五)聊天记忆持久化

本文探讨了在实现大模型聊天记忆功能时,如何选择合适的存储介质以替代默认的内存存储方案。内存存储存在数据易丢失、容量受限、扩展性差、安全性低等缺陷,因此需要更可靠的存储方案。文章分析了多种存储介质的优缺点,包括向量数据库、关系型数据库、文档数据库、内存缓存数据库、对象存储和专用记忆管理系统。综合考虑成本、开发复杂度和系统适用性,文档数据库MongoDB被认为是最合适的选择。文章详细介绍了MongoDB的部署、依赖引入、配置文件设置,并通过源码解析展示如何自定义实现ChatMemoryStore接口,完成持久化

持续学习ing 2203

LangChain 入门系列⑤:核心组件之 Tools 快速入门指南

本文介绍了LangChain中Tools的概念及其自定义实现方法。Tools是扩展大语言模型能力的接口,可用于搜索、计算等任务。主要内容包括:1)Tools的基本要素如名称、描述、参数模式等;2)两种自定义工具的方式:使用@tool装饰器(自动获取函数名和文档字符串)或StructuredTool.from_function方法(提供更多配置选项);3)详细示例展示了如何定义工具、设置参数说明及调用工具。文章还提供了工具集成列表的参考链接,帮助开发者扩展LLM功能。

zxb的博客 1129

Langchain 入门教程五:提示词Prompts

提示词:发给大模型的消息。

十三月的博客 170

langchain入门五:知识串联,chain的使用,原来做一个LLM应用如此简单

langchain入门五:知识串联,chain的使用,原来做一个LLM应用如此简单

Langchain的博客 1298

【 LangChain v1.2 入门系列教程】【五】记忆管理,让 Agent 记住对话

本文介绍了LangChain v1.2版本中全新的记忆管理机制。主要内容包括: 记忆机制重构:采用Checkpointer状态管理,对话历史作为Agent状态自动持久化 记忆分类:分为短期记忆(单次对话)和长期记忆(跨会话) 核心机制:通过thread_id实现多用户会话隔离,不同thread_id完全独立 实操演示: 使用InMemorySaver实现短期记忆 通过thread_id区分不同用户对话 手动读取和操作记忆数据 生产建议:使用"用户ID+会话ID"作为thread_id确保

前端(全栈)、AI Agent开发技术分享 1063

大语言模型入门 | LangChain框架主要概念与实例(五)LangChain生成图片

假设我们需要构建一个基于LLM的问答系统,该系统需要从指定的数据源中提取信息以回答用户的问题。我们可以使用LangChain中的数据增强生成功能与外部数据源进行交互,获取所需的数据。然后,将数据输入到LLM中,生成回答。记忆功能可以帮助我们在多次调用之间保持相关状态,从而提高问答系统的性能。此外,我们还可以使用智能代理功能实现系统的自动优化。最后,通过LangChain提供的评估提示和链实现,我们可以对问答系统的性能进行评估和优化。

m0_56255097的博客 978

LangChain入门学习笔记(五)—— Model I/O之Chat Models

generate:用于根据输入的提示语生成结果。_llm_type:返回属性字符串,用来唯一标志模型类型名称。_agenerate:_generate的异步版本。_stream / _astream:流式传输的同步/异步版本。_identifying_params:用于追踪目的的模型参数属性值。

stingfire的博客 2327
上一篇: LangChain入门(四)
下一篇: LangGraph入门(一)
晚风吹北巷
博客等级 码龄4年 120粉丝 15原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值