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 PyPDFLoaderloader = PyPDFLoader("2401.12599v1.pdf")
pages = loader.load_and_split()print(pages[0].page_content)

文档处理器:TextSplitter

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

from langchain_community.document_loaders import PyPDFLoaderloader = 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, ConversationBufferWindowMemoryhistory = 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 ConversationBufferWindowMemorywindow = 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 ChatOpenAImemory = 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**exponentfrom langchain_core.output_parsers import StrOutputParser
from langchain.output_parsers import JsonOutputToolsParsertools = [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 

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

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

相关文章

上位机图像处理和嵌入式模块部署(mcu之芯片选择)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 目前市面上的mcu很多,有国产的,有进口的,总之种类很多。以stm32为例,这里面又包括了stm32f1、stm32…

热爱无解 少年万丈光芒!首席艺人【彭禹锦】登陆第八季完美童模全球赛

2024年7月,一档由IPA模特委员会创办于2017年的王牌少儿模特大赛即将拉开全球总决赛的帷幕!作为家喻户晓的国民赛事——完美童模曾6季荣获CCTV央视新闻报道,以创意引领、美学引领、和兼具文化底蕴的赛事特色,收获了全球百万亲子家庭的喜爱。20…

深度学习之基于Pytorch+Flask Web框架预测手写数字

欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 随着人工智能和深度学习的快速发展,手写数字识别已成为一个重要的应用领域。该项目…

Python 实现批量文件重命名工具

在现代软件开发中,图形用户界面 (GUI) 工具的创建是一个常见需求。对于那些需要频繁处理文件的任务,拥有一个简便的 GUI 工具尤为重要。在这篇博客中,我们将介绍如何使用 wxPython 创建一个简单的批量文件重命名工具。该工具可以选择一个文件…

Web开发——HTMLCSS

1、概述 Web开发分前端开发和后端开发,前端开发负责展示数据,后端开发负责处理数据。 HTML&CSS是浏览器数据展示相关的内容。 1)网页的组成部分 文字、图片、音频、视频、超链接、表格等等 2)网页背后的本质 程序员写的前端…

重大活动网络安全保障建设及运营指南

在当今高度数字化的社会中,各类重大活动如会议、展览、赛事及庆典等正面临着日益复杂和严峻的网络安全威胁。这些威胁不限于网络入侵或数据泄露,更涉及到对基础设施、关键信息系统和公众舆论的复杂攻击,需要国际社会的密切合作和长期关注。因…

一张图看懂大模型性价比:能力、价格、并发量全面PK

最近,国内云厂商的大模型掀起一场降价风暴。火山引擎、阿里云、百度云等纷纷宣布降价,部分模型价格降幅据称高达99%,甚至还有些模型直接免费。 五花八门的降价话术,一眼望去遍地黄金。但事实真的如此吗?今天我们就拨开…

统计信号处理基础 习题解答10-2

题目 两个随机变量x和y,如果联合PDF分解为: 那么称他们为条件独立的。在上式中z是条件随机变量。 我们观察 其中, , 是相互独立的。证明和是条件独立的。给出条件变量是A。和是无条件独立么?也就是 成立么?为了回答这个问题&…

秋招突击——算法打卡——5/24——两数之和

题目描述 实现代码 ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {int addNumber 0;// 表示进位ListNode* res ListNode();ListNode* curNode res;while(l1 && l2){curNode.value (l1.value l2.value addNumber) % 10 addNumber (l1.value l2.value…

如何快速增加外链?

要快速增加外链并不难,相信各位都知道,难的是快速增加外链且没有风险,所以这时候GNB外链的重要性就出现了,这是一种自然的外链,何谓自然的外链,在谷歌的体系当中,自然外链指的就是其他网站资源给…

mysql 、oss 结合使用

以下是一个使用 Express、MySQL、OSS 和 axios 的 Node.js 示例。这个示例创建了一个 Express 服务器,该服务器有一个路由用于处理视频上传的请求。视频文件首先被上传到 OSS,然后视频的 OSS URL 被存储到 MySQL 数据库。 首先,我们需要安装必…

最大负载1kg!高度模块化设计!大象机器人智能遥控操作机械臂组合myArm MC

引入 近年来,市面上涌现了许多类似于斯坦福大学的 Alopha 机器人项目,这些项目主要通过模仿人类的运动轨迹来进行学习,实现了仿人类的人工智能。Alopha 机器人通过先进的算法和传感技术,能够精确复制人类的动作,并从中…

【stm32】——使用HAL库点灯

目录 一、安装STM32CubeMX 二、HAL库点亮LED灯 1.STM32CubeMX创建项目 2.进入Keil编写代码 三、采用中断控制LED灯的亮灭 1.创建项目 2.Keil中编写代码 四、Keil波形分析 总结 一、安装STM32CubeMX 具体操作可以参考下面链接: 搭建STM32开发环境——STM32CubeMX&a…

用Python pynput库捕捉每一次组合键的优雅舞步

哈喽,大家好,我是木头左! 一、初识pynput:键盘与鼠标的监控利器 当谈论计算机交互时,键盘和鼠标无疑是最常用的设备。无论是编写代码、浏览网页还是玩游戏,都依赖于这些输入设备与机器沟通。但在一些特殊的…

蓝桥杯单片机-国赛5——各外设时序记忆方法及手册重点提取

蓝桥杯涉及三个通讯外设,时序很难记忆,因此本文重点在于记录如何不背时序,直接看手册 注意:本文参考2024年第15届比赛手册讲解,没有英文基础也没关系 每一个外设会在第一张时序图中详细讲解,后续简写&…

网站工作原理

web发展史 1.0时代不可修改 2.0可修改,比如发微博 有以下问题: 课程2: 静态页面 html 动态页面 php 经过服务端的语言解释器,解析成html文件,剩下的就和静态流程一样 后面三个是web服务器,语言解释器&…

[Spring Boot]baomidou 多数据源

文章目录 简述本文涉及代码已开源 项目配置pom引入baomidouyml增加dynamic配置启动类增加注解配置结束 业务调用注解DS()TransactionalDSTransactional自定义数据源注解MySQL2 测试调用查询接口单数据源事务测试多数据源事务如果依然使用Transactional会怎样?测试正…

dubbo复习: (6)和springboot集成时的条件路由

根据指定的条件,对不满足条件的请求进行拦截。 比如拦截ip地址为192.168.31.227的请求。只需要在dubbo admin中的条件路由菜单创建相应的规则 enabled: true force: true runtime: true conditions:- host ! 192.168.31.227

vlan综合实验

一,实验拓扑: 二,实验需求: 1、pc1和pc3所在接口为access;属于vlan 2;P可以骄问pcepces不的一段;其中pc2可以访问pc4/pc5/pc6; 2、pc1/pc3与pc2/pc4/pc5/pc6不在同一网段; 3、所有pc通过DHCP获取Ip地址,且pc1/pc3可以…