Langchain:数据连接封装、缓存封装和LCEL学习和探索

🌵  目录  🌵

😋 数据连接封装 🍔

文档加载器:Document Loaders

文档处理器:TextSplitter

 向量数据库与向量检索

 总结

🍉 缓存封装:Memory 🏖️

对话上下文:ConversationBufferMemory

只保留一个窗口的上下文:ConversationBufferWindowMemory 

通过Token数控制上下文长度:ConversationTokenBufferMemory

更多尝试

总结

🌞 Chain 和 LangChain Expression Language (LCEL) 🔆

Pipeline 式调用 PromptTemplate, LLM 和 OutputParser

用 LCEL 实现 RAG

通过 LCEL 实现 Function Calling

通过 LCEL,还可以实现


        本文将继续延续Langchain专栏文章,本文将讲解Langchain的数据连接封装、缓存封装和LCEL,逐渐深入学习Langchain的高级能力,帮助我们更好更快的接触大模型。

        初识Langchain可以看看这篇文章:

直通车:LangChain:大模型框架的深度解析与应用探索-CSDN博客

😋数据连接封装

        对外部进行加载,如果你需要可以做一些处理、转换,可以做embedding然后放在store(向量数据)里,然后通过retrieve访问向量数据库形式进行检索。这是一个外部数据连接进来的一个模块的划分和逻辑上 的Pipeline。

文档加载器:Document Loaders

# pip install pypdf
from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader("2401.12599v1.pdf")
pages = loader.load_and_split()

print(pages[0].page_content)

文档处理器:TextSplitter

        切割器,按照字符切割。示例代码,真正要使用实现的粒度要比该示例粒度都要细。

from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader("2401.12599v1.pdf")
pages = loader.load_and_split()

print(pages[0].page_content)

from langchain.text_splitter import RecursiveCharacterTextSplitter

# 文档切分
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=300,
    chunk_overlap=200, 
    length_function=len,
    add_start_index=True,
)

paragraphs = text_splitter.create_documents([pages[0].page_content])
for para in paragraphs:
    print(para.page_content)
    print('-------')

LangChain 的 PDFLoader 和 TextSplitter 实现都比较粗糙,实际生产中不建议使用。

 向量数据库与向量检索

        它封装了和三方数据库的链接和检索。因为本身就是一个接口的封装,比如调用chroma是一套接口协议,调用pytorch是一套接口协议,它都不一样,通过Langchain封装了你都可以用同一套接口去访问向量数据库了,将来我要换一个向量数据库,我都不需要大规模的改代码都能使用同一套接口去实现。

# pip install chromadb
# 加载 .env 到环境变量
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())

from langchain.document_loaders import UnstructuredMarkdownLoader
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain_community.document_loaders import PyPDFLoader

# 加载文档
loader = PyPDFLoader("2401.12599v1.pdf")
pages = loader.load_and_split()

# 文档切分
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=300,
    chunk_overlap=100,
    length_function=len,
    add_start_index=True,
)

texts = text_splitter.create_documents([pages[2].page_content,pages[3].page_content])

# 灌库
embeddings = OpenAIEmbeddings()
db = Chroma.from_documents(texts, embeddings)

# LangChain内置的 RAG 实现
qa_chain = RetrievalQA.from_chain_type(
    llm=ChatOpenAI(temperature=0),
    retriever=db.as_retriever()
)

query = "llm基于哪些数据来训练?"
response = qa_chain.invoke(query)
print(response["result"])

# LLM(Large Language Models)基于大量的文本数据进行训练。这些数据可以包括互联网上的网页、书籍、新闻文章、论文等各种文本资源。通过对这些数据进行深度学习训练,LLM可以学习到丰富的语言知识和模式,从而具备处理和生成文本的能力。

更多的三方检索组件链接,参考:Vector stores | 🦜️🔗 LangChain

 总结

  1. 文档处理部分 LangChain 实现较为粗糙,实际生产中不建议使用
  2. 与向量数据库的链接部分本质是接口封装,向量数据库需要自己选型

🍉缓存封装:Memory

对话上下文:ConversationBufferMemory

        它的一个特点是通过一个Buffer存储上下文,并且可以存储多行会话,当需要展示历史会话是可以按写入顺序全部打印出来。

from langchain.memory import ConversationBufferMemory, ConversationBufferWindowMemory

history = ConversationBufferMemory()
history.save_context({"input": "hello"}, {"output": "hello"})
print(history.load_memory_variables({}))
history.save_context({"input": "nice to meet you"}, {"output": "nice to meet you too"})
print(history.load_memory_variables({}))  
history.save_context({"input": "good bye"}, {"output": "good bye"})
print(history.load_memory_variables({}))  

# {'history': 'Human: hello\nAI: hello'}
# {'history': 'Human: hello\nAI: hello\nHuman: nice to meet you\nAI: nice to meet you too'}
# {'history': 'Human: hello\nAI: hello\nHuman: nice to meet you\nAI: nice to meet you too\nHuman: good bye\nAI: bye'}

只保留一个窗口的上下文:ConversationBufferWindowMemory 

        与ConversationBufferMemory不同的时候它能根据预设的会话大小进行截断,不会造成因为会话太多导致报错。这种只能保证轮数,但不能保证不会报错。

from langchain.memory import ConversationBufferWindowMemory

window = ConversationBufferWindowMemory(k=2)# 保留多少轮问答
window.save_context({"input": "hello"}, {"output": "hello"})
print(window.load_memory_variables({}))
window.save_context({"input": "nice to meet you"}, {"output": "nice to meet you too"})
print(window.load_memory_variables({}))
window.save_context({"input": "how old are you?"}, {"output": "#!@!#!!¥"})
print(window.load_memory_variables({}))
window.save_context({"input": "good bye"}, {"output": "bye"})
print(window.load_memory_variables({}))

# {'history': 'Human: hello\nAI: hello'}
# {'history': 'Human: hello\nAI: hello\nHuman: nice to meet you\nAI: nice to meet you too'}
# {'history': 'Human: nice to meet you\nAI: nice to meet you too\nHuman: how old are you?# \nAI: #!@!#!!¥'}
# {'history': 'Human: how old are you?\nAI: #!@!#!!¥\nHuman: good bye\nAI: bye'}

通过Token数控制上下文长度:ConversationTokenBufferMemory

        能够限制Memory最多能存多少个Token,超过Token就遗弃,它可以保证调用会话不会报错。

# 加载 .env 到环境变量
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())

from langchain.memory import ConversationTokenBufferMemory
from langchain_openai import ChatOpenAI

memory = ConversationTokenBufferMemory(
    llm=ChatOpenAI(),
    # max_token_limit=40
)
memory.save_context(
    {"input": "你好啊"}, {"output": "你好,我是你的AI助手。"})
memory.save_context(
    {"input": "你会干什么"}, {"output": "我什么都会"})

print(memory.load_memory_variables({}))
# {'history': 'Human: 你会干什么\nAI: 我什么都会'}

更多尝试

ConversationSummaryMemory: 对上下文做摘要

传送门:Conversation Summary | 🦜️🔗 LangChain

ConversationSummaryBufferMemory: 保存 Token 数限制内的上下文,对更早的做摘要

传送门:Conversation Summary Buffer | 🦜️🔗 LangChain

VectorStoreRetrieverMemory: 将 Memory 存储在向量数据库中,根据用户输入检索回最相关的部分

传送门:Backed by a Vector Store | 🦜️🔗 LangChain

总结

  1. LangChain 的 Memory 管理机制属于可用的部分,尤其是简单情况如按轮数或按 Token 数管理;
  2. 对于复杂情况,它不一定是最优的实现,例如检索向量库方式,建议根据实际情况和效果评估;
  3. 但是它对内存的各种维护方法的思路在实际生产中可以借鉴

🌞Chain 和 LangChain Expression Language (LCEL)

        LangChain Expression Language(LCEL)是一种声明式语言,可轻松组合不同的调用顺序构成 Chain。LCEL 自创立之初就被设计为能够支持将原型投入生产环境,无需代码更改,从最简单的“提示+LLM”链到最复杂的链(已有用户成功在生产环境中运行包含数百个步骤的 LCEL Chain)。

LCEL 的一些亮点包括:

  1. 流支持:使用 LCEL 构建 Chain 时,你可以获得最佳的首个令牌时间(即从输出开始到首批输出生成的时间)。对于某些 Chain,这意味着可以直接从 LLM 流式传输令牌到流输出解析器,从而以与 LLM 提供商输出原始令牌相同的速率获得解析后的、增量的输出。

  2. 异步支持:任何使用 LCEL 构建的链条都可以通过同步 API(例如,在 Jupyter 笔记本中进行原型设计时)和异步 API(例如,在 LangServe 服务器中)调用。这使得相同的代码可用于原型设计和生产环境,具有出色的性能,并能够在同一服务器中处理多个并发请求。

  3. 优化的并行执行:当你的 LCEL 链条有可以并行执行的步骤时(例如,从多个检索器中获取文档),我们会自动执行,无论是在同步还是异步接口中,以实现最小的延迟。

  4. 重试和回退:为 LCEL 链的任何部分配置重试和回退。这是使链在规模上更可靠的绝佳方式。目前我们正在添加重试/回退的流媒体支持,因此你可以在不增加任何延迟成本的情况下获得增加的可靠性。

  5. 访问中间结果:对于更复杂的链条,访问在最终输出产生之前的中间步骤的结果通常非常有用。这可以用于让最终用户知道正在发生一些事情,甚至仅用于调试链条。你可以流式传输中间结果,并且在每个 LangServe 服务器上都可用。

  6. 输入和输出模式:输入和输出模式为每个 LCEL 链提供了从链的结构推断出的 Pydantic 和 JSONSchema 模式。这可以用于输入和输出的验证,是 LangServe 的一个组成部分。

  7. 无缝 LangSmith 跟踪集成:随着链条变得越来越复杂,理解每一步发生了什么变得越来越重要。通过 LCEL,所有步骤都自动记录到 LangSmith,以实现最大的可观察性和可调试性。

  8. 无缝 LangServe 部署集成:任何使用 LCEL 创建的链都可以轻松地使用 LangServe 进行部署。 

直通车:LangChain Expression Language (LCEL) | 🦜️🔗 LangChain

 Pipeline 式调用 PromptTemplate, LLM 和 OutputParser

from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())

from langchain.output_parsers import PydanticOutputParser
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema import StrOutputParser
from langchain.schema.runnable import RunnablePassthrough
from pydantic import BaseModel, Field, validator
from typing import List, Dict, Optional
from enum import Enum

# 输出结构
class SortEnum(str, Enum):
    data = 'data'
    price = 'price'

class OrderingEnum(str, Enum):
    ascend = 'ascend'
    descend = 'descend'

class Semantics(BaseModel):
    name: Optional[str] = Field(description="流量包名称",default=None)
    price_lower: Optional[int] = Field(description="价格下限",default=None)
    price_upper: Optional[int] = Field(description="价格上限",default=None)
    data_lower: Optional[int] = Field(description="流量下限",default=None)
    data_upper: Optional[int] = Field(description="流量上限",default=None)
    sort_by: Optional[SortEnum] = Field(description="按价格或流量排序",default=None)
    ordering: Optional[OrderingEnum] = Field(description="升序或降序排列",default=None)

# OutputParser
parser = PydanticOutputParser(pydantic_object=Semantics)

# Prompt 模板
prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "将用户的输入解析成JSON表示。输出格式如下:\n{format_instructions}\n不要输出未提及的字段。",
        ),
        ("human", "{query}"),
    ]
).partial(format_instructions=parser.get_format_instructions())

# 模型
model = ChatOpenAI(temperature=0)

# LCEL 表达式
#  | 表示从左到右运行,| 前面的操作符是管道符,它表示将前一个操作符的输出作为下一个操作符的
# 1.拿用户输入的填充到模板中
# 2.将 RunnablePassthrough 对象传递给 prompt,并返回一个新的 RunnablePassthrough 对象
# 3.将新的 RunnablePassthrough 对象传递给 model,并返回一个新的 RunnablePassthrough 对象
# 4.将新的 RunnablePassthrough 对象传递给 parser 格式化输出
runnable = (
    {"query": RunnablePassthrough()} | prompt | model | parser
)

# 运行
print(runnable.invoke("不超过100元的流量大的套餐有哪些"))

注意: 在当前的文档中 LCEL 产生的对象,被叫做 runnable 或 chain,经常两种叫法混用。本质就是一个自定义调用流程。

 使用 LCEL 的价值,也就是 LangChain 的核心价值。
官方从不同角度给出了举例说明:https://python.langchain.com/docs/expression_language/why

通过 LCEL 实现 RAG

from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())

from langchain.prompts import ChatPromptTemplate
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_openai import ChatOpenAI
from langchain_community.document_loaders import PyPDFLoader

# 模型
model = ChatOpenAI(model="gpt-4-turbo", temperature=0)

# 加载文档
loader = PyPDFLoader("2401.12599v1.pdf")
pages = loader.load_and_split()

# 文档切分
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=200,
    chunk_overlap=100,
    length_function=len,
    add_start_index=True,
)

texts = text_splitter.create_documents(
    [page.page_content for page in pages[:4]]
)

# 灌库
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
db = FAISS.from_documents(texts, embeddings)

# 检索 top-1 结果
retriever = db.as_retriever(search_kwargs={"k": 5})


from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import RunnablePassthrough
from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import RunnablePassthrough

# Prompt模板
template = """Answer the question based only on the following context:
{context}

Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)

# Chain
rag_chain = (
    {"question": RunnablePassthrough(), "context": retriever}
    | prompt
    | model
    | StrOutputParser()
)

invoke = rag_chain.invoke("Llama llm基于哪些数据来训练?")
print(invoke)
# 基于上下文的信息,大型语言模型(Large language models,LLM)主要是根据来自公开可用的互联网资源训练的。这些资源包括网页、书籍、新闻和对话文本。因此,Llama LLM是基于这些类型的数据来训练的。

通过 LCEL 实现 Function Calling

from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())

from langchain_openai import ChatOpenAI
# 提供了一个注解器
from langchain_core.tools import tool

# 模型
model = ChatOpenAI(model="gpt-4-turbo", temperature=0)

# 通过这个注解标记一个函数,它将被 LangChain 用来调用和执行。
@tool
def multiply(first_int: int, second_int: int) -> int:
    """两个整数相乘"""
    return first_int * second_int

@tool
def add(first_int: int, second_int: int) -> int:
    """两数之和"""
    return first_int + second_int

@tool
def exponentiate(base: int, exponent: int) -> int:
    """乘方"""
    return base**exponent

from langchain_core.output_parsers import StrOutputParser
from langchain.output_parsers import JsonOutputToolsParser

tools = [multiply, add, exponentiate]

# 通过语言模型调用function calling
# 带有分支的 LCEL
# 它会将你定义的函数,生成openai的schema,然后调用openai的模型
llm_with_tools = model.bind_tools(tools) | {
    # llm自己去判断是返回function calling的指示还是返回文本回复
    "functions": JsonOutputToolsParser(),
    "text": StrOutputParser()
}

# result = llm_with_tools.invoke("1024的16倍是多少")
# result = llm_with_tools.invoke("1+1等于多少")
result = llm_with_tools.invoke("你是谁")
print(result)
# {'functions': [{'args': {'first_int': 1024, 'second_int': 16}, 'type': 'multiply'}], 'text': ''}
# {'functions': [{'args': {'first_int': 1, 'second_int': 1}, 'type': 'add'}], 'text': ''}
# {'functions': [], 'text': '我是一个聊天机器人。我可以回答你的问题,提供帮助和建议。\n我是一个聊天机器人。我可以回答你的问题,提供帮助和建议。'}

注意:如果执行报NotImplementedError错误,可以更新langchain版本试试:

python -m pip install --upgrade pip

pip install -qU langchain-openai

更多实现

  1. 配置运行时变量:Configure runtime chain internals | 🦜️🔗 LangChain
  2. 故障回退:Fallbacks | 🦜️🔗 LangChain
  3. 并行调用:Parallel: Format data | 🦜️🔗 LangChain
  4. 逻辑分支:Route logic based on input | 🦜️🔗 LangChain
  5. 调用自定义流式函数:Lambda: Run custom functions | 🦜️🔗 LangChain
  6. 链接外部 Memory:Add message history (memory) | 🦜️🔗 LangChain

更多例子:LangChain Expression Language (LCEL) | 🦜️🔗 LangChain 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

urllib_post请求_百度翻译

打开百度翻译,并打开控制台,输入spider,然后在网络中找到对应的接口,可以看出,该url是post请求 在此案例中找到的接口为sug,依据为: 可以看到,传递的数据为kw : XXX, 所…

Hadoop3:HDFS的Fsimage和Edits文件介绍

一、概念 Fsimage文件:HDFS文件系统元数据的一个永久性的检查点,其中包含HDFS文件系统的所有目 录和文件inode的序列化信息。 Edits文件:存放HDFS文件系统的所有更新操作的路径,文件系统客户端执行的所有写操作首先 会被记录到Ed…

移动云ECS主机:未来云计算的驱动力

文章目录 前言一、移动云云主机ECS云主机ECS产品优势云主机ECS产品功能云主机ECS应用场景 二、移动云云主机ECS选购三、移动云云主机ECS配置四、移动云云主机ECS牛刀小试五、移动云云主机ECS安装部署消息中间件RocketMQ云主机ECS安装RocketMQ云主机ECS配置RocketMQ云主机ECS启动…

如何做好云安全防护

随着云计算技术的迅猛发展和普及,越来越多的企业和个人选择将数据和业务应用迁移到云平台,以享受其带来的高效、便捷和可扩展性。然而,云环境的复杂性和开放性也带来了前所未有的安全挑战。如何确保云环境中的数据安全,成为了每一…

【Linux】lsblk 命令使用

lsblk 命令 lsblk 是一个在 Linux 系统中用来列出所有可用的块设备(例如硬盘驱动器、固态硬盘、USB 驱动器等)的命令行工具。它提供了关于这些设备的详细信息,包括它们的名称、大小、类型、挂载点等。 语法 lsblk [选项] 选项及作用 执行…

LabVIEW高温往复摩擦测试系统中PID控制

在LabVIEW开发高温往复摩擦测试系统中实现PID控制,需要注意以下几个方面: 1. 系统建模与参数确定 物理模型建立: 首先,需要了解被控对象的物理特性,包括热惯性、摩擦系数等。这些特性决定了系统的响应速度和稳定性。实验数据获取…

PVE 虚拟机环境下删除 local-lvm分区

1、删除逻辑卷 lvremote pve/data 2、扩展逻辑卷 lvextend -l 100%FREE -r pve/root 3、 修改存储目录内容 点击 Datacenter - Storage (1)删除local-lvm分区 (2)编辑local分区,在内容一项中勾选所有可选项。

黑龙江等保测评深入理解

“没有网络安全,就没有国家安全”,等级保护测评是指按照网络安全系统制定的一系列的防护过程,对已经有的和即将上线的商业服务的基础设施(系统,数据库,中间件等)所做的一系列的检查,…

Thinkphp3.2.3网站后台不能访问如何修复

我是使用Thinkphp3.2.3新搭建的PHP网站,但是网站前台可以访问,后台访问出现如图错误: 由于我使用的Hostease的Linux虚拟主机产品默认带普通用户权限的cPanel面板,对于上述出现的问题不清楚如何处理,因此联系Hostease的…

第3天 Web源码拓展_小迪网络安全笔记

1.关于web源码目录结构 #数据库配置文件 后台目录 模板目录 数据库目录 1.1数据库配置文件: 1.1就拿wordpress来说,先到官网下载源码:Download – WordPress.org,解压源码之后: 2.2找到目录下名为 wp-config-sample.php的文件,这就是数据库配置文件: 设想: 我们在渗透…

如何将word插入的形状转成图片(高清)导出?

文章目录 前言(不感兴趣可以直接看正文)一、新建画布二、插入形状三、复制四、粘贴为图片五、另存为总结 前言(不感兴趣可以直接看正文) 因为我毕业论文里的图片刚开始使用画图软件画的,但到后期论文即将胶印的时候&a…

Agent将如何影响和重塑企业服务市场?

在Sam Altman、吴恩达等几位AI业界人士的“带货”之下,Agent作为新一代生产力工具的巨大潜力和广泛的应用前景终于“破圈”、被更多的看到和讨论。其实在2023年时,我就预测过,2024年会是大语言模型应用落地和Agent的元年。 为什么Agent会是大…

从零到一:手把手教你将项目部署上线-环境准备

部署步骤 引言1.Java环境配置2.ngnix安装好书推荐 引言 将自己的项目从本地开发环境顺利部署上线,是每个开发者必经的里程碑。今天,我们就从零开始,一步一步教你如何将手中的项目部署到线上,让全世界见证你的创造力。 首先&#x…

第十八篇:探索非关系型数据库:从入门到实践

探索非关系型数据库:从入门到实践 1. 引言 1.1 非关系型数据库的崛起:背景与重要性 在过去的几十年里,关系型数据库(RDBMS)一直在数据存储和管理领域占据主导地位。其严谨的结构化数据模型以及强大的事务处理能力&am…

只需提交几次代码就能轻松实现系统级的变更!——“基础设施即代码”模式与实践

“基础设施即代码”模式与实践 基础设施即代码(Infrastructure as Code,IaC)是指利用脚本、配置或编程语言创建和维护基础设施的一组实践和流程。通过IaC,我们可以轻松测试各个组件、实现所需的功能并在最小化停机时间的前提下进行…

语义化版本规范

Releases 是指软件或项目的正式发布版本,在浏览一些开源仓库时,可以看到当前项目最新版本和历史版本 仔细研究就会发现,版本号不是以固定值递增的,有时候第三位加 1,有时候加 2,有时候直接把第一位加 1&…

一文了解微服务

微服务架构是一种将应用程序划分为一组小型、独立的服务的方法,这些服务运行在自己的进程中,通常通过网络进行通信。微服务架构的主要优点是可以提高应用程序的灵活性和可扩展性,同时也使得开发、部署和维护更加容易。本文将介绍微服务架构的…

C语言 数组——查找算法的函数实现

目录 线性查找(Linear Search) 线性查找的性能 猜数游戏 二分查找(Binary Search) 并非吹毛求疵,鸡蛋里挑骨头 二分查找的性能 线性查找(Linear Search) 不 要求数据表是已排好序的  …

React-JSX基础

什么是JSX 概念:JSX是JavaScript和XML(HTML)的缩写,表示在JS代码中编写HTML模板结构,它是React中编写UI模板的方式 优势:1.HTML的声明式模板写法 2.JS的可编程能力 JSX的本质 JSX并不是标准的JS语法&…