一篇文章带你了解LangChain框架

目录

一、初识LangChain

1.1 LangChain介绍

1.2 LangChain基本使用

基本使用

使用提示模板

使用输出解析器

向量存储

RAG+Langchain

二、LangChain之Model I/O

2.1 Model I/O

2.2 提示模板

提示模板概念讲解

提示模板案例讲解

模板导入

创建提示模板

使用提示模板

ChatPromptTemplate

少量样本示例

三、LangChain之Chain链

3.1 链的基本使用

3.2 使用表达式语言 (LCEL)

3.3 案例演示

3.4 链的调用

invoke方法

predict方法

batch 方法

3.5 官方工具链

文档链

数学链

SQL查询链

四、LangChain之RAG

4.1 文档加载

4.2 文档切割

4.3 文本向量化

4.4 向量存储

4.5 检索器


一、初识LangChain

1.1 LangChain介绍

LangChain是一个基于大语言模型用于构建端到端语言模型应用的框架,它提供了一系列工具、套件和接口,让开发者使用语言模型来实现各种复杂的任务,如文本到图像的生成、文档问答、聊天机器人等。

官网地址:Introduction | 🦜️🔗 LangChain

中文地址:introduction | LangChain中文网

英文描述:

LangChain is a framework for developing applications powered by large language models (LLMs).

LangChain simplifies every stage of the LLM application lifecycle:

- Development: Build your applications using LangChain's open-source building blocks, components, and third-party integrations. Use LangGraph to build stateful agents with first-class streaming and human-in-the-loop support.

- Productionization: Use LangSmith to inspect, monitor and evaluate your chains, so that you can continuously optimize and deploy with confidence.

- Deployment: Turn your LangGraph applications into production-ready APIs and Assistants with LangGraph Cloud.

LangChain简化了LLM应用程序生命周期的各个阶段:

  • 开发阶段:使用LangChain的开源构建块和组件构建应用程序,利用第三方集成和模板快速启动。

  • 生产化阶段:使用LangSmith检查、监控和评估您的链,从而可以自信地持续优化和部署。

  • 部署阶段:使用LangServe将任何链转化为API。

Langchain的核心组件:

  • 模型(Models):包含各大语言模型的LangChain接口和调用细节,以及输出解析机制。

  • 提示模板(Prompts):使提示工程流线化,进一步激发大语言模型的潜力。

  • 数据检索(Indexes):构建并操作文档的方法,接受用户的查询并返回最相关的文档,轻松搭建本地知识库。

  • 记忆(Memory):通过短时记忆和长时记忆,在对话过程中存储和检索数据,让ChatBot记住你。

  • 链(Chains):LangChain中的核心机制,以特定方式封装各种功能,并通过一系列的组合,自动而灵活地完成任务。

  • 代理(Agents):另一个LangChain中的核心机制,通过“代理”让大模型自主调用外部工具和内部工具,使智能Agent成为可能。

开源库组成:

  • langchain-core :基础抽象和LangChain表达式语言

  • langchain-community :第三方集成。合作伙伴包(如langchain-openai、langchain-anthropic等),一些集成已经进一步拆分为自己的轻量级包,只依赖于langchain-core

  • langchain :构成应用程序认知架构的链、代理和检索策略

  • langgraph:通过将步骤建模为图中的边和节点,使用 LLMs 构建健壮且有状态的多参与者应用程序

  • langserve:将 LangChain 链部署为 REST API

  • LangSmith:一个开发者平台,可让您调试、测试、评估和监控LLM应用程序,并与LangChain无缝集成

安装LangChain

安装指定版本的LangChain

pip install langchain==0.3.7  -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install langchain-openai==0.2.3  -i https://pypi.tuna.tsinghua.edu.cn/simple

1.2 LangChain基本使用

基本使用

LangChain的基本使用,和前面的OpenAI的使用是差不多的。

from langchain_openai import ChatOpenAI

# 初始化 ChatOpenAI 客户端
llm = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="XXXXXXXXXXXXXXXXX",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
)

# 直接提供问题,并调用 LLM
response = llm.invoke("什么是大模型?还有你的模型版本是多少?")
print(response)
print(response.content)

使用提示模板

我们在使用LangChain的时候也可以使用提示词模板,具体的简单用法如下:

from langchain_openai import ChatOpenAI


llm = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="XXXXXXXXXXXXXXXXXXXXX",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
)

# 我们也可以创建prompt template, 并引入一些变量到prompt template中,这样在应用的时候更加灵活
from langchain_core.prompts import ChatPromptTemplate

# 需要注意的一点是,这里需要指明具体的role,在这里是system和用户
prompt = ChatPromptTemplate.from_messages([
    ("system", "您是世界级的技术文档编写者"),
    ("user", "{input}")  # {input}为变量
])

# 我们可以把prompt和具体llm的调用和在一起(通过chain,chain可以理解为sequence of calls to take)  Linux  ps aux | grep redis 管道符号
chain = prompt | llm
response = chain.invoke({"input": "大模型中的LangChain是什么?"})
print(response)
print(response.content)

使用输出解析器

对于大模型输出的内容如果需要特点的格式输出,我们可以指定特定的解析器来实现这个功能。

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser

# 初始化模型
llm = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="XXXXXXXXXXXXXXXXXXXXXXXXXX",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
)

# 创建提示模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "您是世界级的技术文档编写者。"),
    ("user", "{input}")
])

# 使用输出解析器
#output_parser = StrOutputParser()
output_parser = JsonOutputParser()

# 将其添加到上一个链中
chain =  prompt | llm | output_parser
#chain = prompt | llm

# 调用它并提出同样的问题。答案是一个字符串,而不是ChatMessage
# chain.invoke({"input": "LangChain是什么?"})
response = chain.invoke({"input": "LangChain是什么? 问题用question 回答用answer 用JSON格式回复"})
print(response)

向量存储

使用一个简单的本地向量存储 FAISS,首先需要安装它。

pip install faiss-cpu

pip install langchain_community==0.3.7

RAG+Langchain

上面我们介绍了向量存储,我们可以基于外部知识,增强大模型回复来把 RAG 的功能实现出来。

import os
from langchain_community.document_loaders import WebBaseLoader
import bs4
from openai import OpenAI  # 导入阿里云兼容的 openai 客户端
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import ChatOpenAI
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate
from langchain.chains import create_retrieval_chain
from langchain_core.embeddings import Embeddings

# 设置环境变量
os.environ["USER_AGENT"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
DASHSCOPE_API_KEY = "XXXXXXXXXXXX"  # 替换为你的 API Key

# 初始化阿里云客户端
client = OpenAI(
    api_key=DASHSCOPE_API_KEY,
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)


# 自定义嵌入类
class CustomEmbeddings(Embeddings):
    def __init__(self, client):
        self.client = client

    def embed_documents(self, texts):
        """
        将多个文档嵌入为向量。
        :param texts: 文本列表
        :return: 嵌入向量列表
        """
        embeddings_list = []
        for text in texts:
            response = self.client.embeddings.create(
                model="text-embedding-v3",  # 替换为你需要使用的模型名称
                input=text,
                dimensions=1024,  # 可选参数,根据需求调整
                encoding_format="float",  # 可选参数,根据需求调整
            )
            embeddings_list.append(response.data[0].embedding)
        return embeddings_list

    def embed_query(self, text):
        """
        将单个查询嵌入为向量。
        :param text: 查询文本
        :return: 嵌入向量
        """
        response = self.client.embeddings.create(
            model="text-embedding-v3",  # 替换为你需要使用的模型名称
            input=text,
            dimensions=1024,  # 可选参数,根据需求调整
            encoding_format="float",  # 可选参数,根据需求调整
        )
        return response.data[0].embedding


# 加载网页内容
loader = WebBaseLoader(
    web_path="https://www.gov.cn/xinwen/2020-06/01/content_5516649.htm",
    bs_kwargs=dict(parse_only=bs4.SoupStrainer(id="UCAP-CONTENT"))
)
docs = loader.load()

# 分割文档
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
documents = text_splitter.split_documents(docs)

# 过滤掉空文档
documents = [doc for doc in documents if doc.page_content.strip()]

# 获取文本内容列表
texts = [doc.page_content for doc in documents]

# 使用自定义嵌入类生成嵌入向量
embeddings = CustomEmbeddings(client)
embeddings_vectors = embeddings.embed_documents(texts)

# 创建 FAISS 向量存储
vector = FAISS.from_embeddings(
    text_embeddings=list(zip(texts, embeddings_vectors)),
    embedding=embeddings,
)

# 初始化语言模型
llm = ChatOpenAI(
    api_key=DASHSCOPE_API_KEY,
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
)

# 定义提示模板
prompt = ChatPromptTemplate.from_template("""仅根据提供的上下文回答以下问题:

<context>
{context}
</context>

问题: {input}""")

# 创建文档组合链
document_chain = create_stuff_documents_chain(llm, prompt)

# 创建检索器
retriever = vector.as_retriever()
retriever.search_kwargs = {"k": 3}  # 限制为最多检索3个文档

# 创建检索链
retrieval_chain = create_retrieval_chain(retriever, document_chain)

# 调用检索链并获取回答
response = retrieval_chain.invoke({"input": "建设用地使用权是什么?"})
print(response["answer"])

二、LangChain之Model I/O

2.1 Model I/O

可以把对模型的使用过程拆解成三块: 输入提示(Format)、调用模型(Predict)、输出解析(Parse)

  • 1.提示模板: LangChain的模板允许动态选择输入,根据实际需求调整输入内容,适用于各种特定任务和应用。

  • 2.语言模型: LangChain 提供通用接口调用不同类型的语言模型,提升了灵活性和使用便利性。

  • 3.输出解析: 利用 LangChain 的输出解析功能,精准提取模型输出中所需信息,避免处理冗余数据,同时将非结构化文本转换为可处理的结构化数据,提高信息处理效率。

这三块形成了一个整体,在LangChain中这个过程被统称为Model I/O。针对每块环节,LangChain都提供了模板和工具,可以帮助快捷的调用各种语言模型的接口

2.2 提示模板

提示模板概念讲解

在LangChain的Model I/O中,提示模板是其组成之一,语言模型的提示是用户提供的一组指令或输入,用于指导模型的响应,帮助模型理解上下文并生成相关且连贯的基于语言的输出,例如回答问题、完成句子或参与某项活动、对话。

PromptTemplates 是LangChain中的一个概念,通过接收原始用户输入,并返回一个准备好传递给语言模型的信息(即提示词 prompt)

通俗点说,prompt template 是一个模板化的字符串,可以用来生成特定的提示(prompts)。你可以将变量插入到模板中,从而创建出不同的提示。这对于重复生成相似格式的提示非常有用,尤其是在自动化任务中。

LangChain提示模板特点

  1. 清晰易懂的提示: 提高提示文本的可读性,使其更易于理解,尤其是在处理复杂或涉及多个变量的情况下。

  2. 增强可重用性: 使用模板,可以在多个地方重复使用,简化代码,无需重复构建提示字符串。

  3. 简化维护: 使用模板后,如果需要更改提示内容,只需修改模板,无需逐个查找所有用到该提示的地方。

  4. 智能处理变量: 模板可以自动处理变量的插入,无需手动拼接字符串。

  5. 参数化生成: 模板可以根据不同的参数生成不同的提示,有助于个性化文本生成。

LangChain提示模板类型

  • 1.LLM提示模板 PromptTemplate:常用的String提示模板

  • 2.聊天提示模板 ChatPromptTemplate: 常用的Chat提示模板,用于组合各种角色的消息模板,传入聊天模型。消息模板包括:ChatMessagePromptTemplate、HumanMessagePromptTemplate、AIlMessagePromptTemplate、SystemMessagePromptTemplate等

  • 3.样本提示模板 FewShotPromptTemplate:通过示例来教模型如何回答

  • 4.部分格式化提示模板:提示模板传入所需值的子集,以创建仅期望剩余值子集的新提示模板。

  • 5.管道提示模板 PipelinePrompt: 用于把几个提示组合在一起使用。

  • 6.自定义模板:允许基于其他模板类来定制自己的提示模板。

提示模板案例讲解

模板导入

我们需要使用LangChain的提示词模板我们需要在代码中导入模板,具体的导入代码为:

from langchain.prompts.prompt import PromptTemplate
from langchain.prompts import FewShotPromptTemplate
from langchain.prompts.pipeline import PipelinePromptTemplate
from langchain.prompts import ChatPromptTemplate
from langchain.prompts import (
    ChatMessagePromptTemplate,
    SystemMessagePromptTemplate,
    AIMessagePromptTemplate,
    HumanMessagePromptTemplate,
)

创建提示模板

我们先来看看原始模板的应用

# 导入LangChain中的提示模板
from langchain.prompts import PromptTemplate

# 创建原始模板
template = "您是一位专业的程序员。\n对于信息 {text} 进行简短描述"

# 根据原始模板创建LangChain提示模板
prompt = PromptTemplate.from_template(template)

# 打印LangChain提示模板的内容
print(prompt)
print("="*50)
print(prompt.format(text="langchain"))

然后我们来看看如何直接生成提示模板

from langchain.prompts import PromptTemplate

prompt = PromptTemplate(
    input_variables=["text"],
    template="您是一位专业的程序员。\n对于信息 {text} 进行简短描述"
)
print(prompt.format(text="langchain"))

使用提示模板

调用语言模型,让模型帮写答案,并返回结果。

# 导入LangChain中的OpenAI模型接口
from langchain_openai import ChatOpenAI

from langchain.prompts import PromptTemplate
# 创建模型实例
model = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
)

prompt = PromptTemplate(
    input_variables=["text"],
    template="您是一位专业的程序员。\n对于信息 {text} 进行简短描述"
)
# 输入提示
input = prompt.format(text="大模型 langchain")

# 得到模型的输出
output = model.invoke(input)
# output = model.invoke("您是一位专业的程序员。对于信息 langchain 进行简短描述")

# 打印输出内容
print(output)

ChatPromptTemplate

然后我们再分别给大家介绍下其他的提示词模板,先来看看ChatPromptTemplate聊天提示模板的使用。

PromptTemplate创建字符串提示的模板。默认情况下,使用Python的str.format语法进行模板化。而ChatPromptTemplate是创建聊天消息列表的提示模板。创建一个ChatPromptTemplate提示模板,模板的不同之处是它们有对应的角色。

from langchain.prompts.chat import ChatPromptTemplate

# template = "你是一个数学家,你可以计算任何算式"
template = "你是一个翻译专家,擅长将 {input_language} 语言翻译成 {output_language}语言."
human_template = "{text}"

chat_prompt = ChatPromptTemplate.from_messages([
    ("system", template),
    ("human", human_template),
])
# print(chat_prompt)
# exit()
# 导入LangChain中的ChatOpenAI模型接口
from langchain_openai import ChatOpenAI

# 创建模型实例
model = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
)
# 输入提示
# messages = chat_prompt.format_messages(text="我今年18岁,我的舅舅今年38岁,我的爷爷今年72岁,我和舅舅一共多少岁了?")
# print(messages)
messages = chat_prompt.format_messages(input_language="英文", output_language="中文", text="I love Large Language Model.")
print(messages)
# 得到模型的输出
output = model.invoke(messages)
# 打印输出内容
print(output.content)

LangChain 中,ChatPromptTemplatePromptTemplate 是用于生成提示词(prompts)的两种不同模板类型,它们的主要差异在于设计的目的和使用场景:

  1. PromptTemplate:

    • 用途PromptTemplate 主要是为标准的文本生成任务设计的,用于生成非对话式的提示词。它可以用于各种文本生成任务,如文章撰写、摘要生成、问题回答等。

    • 格式:它通常用于生成一次性或非交互式的输出,不需要维持对话的状态。

    • 功能PromptTemplate 允许用户插入变量,根据模板生成最终的提示词。这些变量可以是静态的,也可以是动态生成的。

  2. ChatPromptTemplate:

    • 用途ChatPromptTemplate 是专门为对话系统设计的,用于生成对话式的提示词。它适用于聊天机器人、对话模型等需要持续对话的场景。

    • 格式:它不仅包含要发送给模型的文本,还包括对话的历史信息,帮助模型理解上下文。

    • 功能ChatPromptTemplate 同样支持变量插入,但它还额外支持对话历史的管理,使得生成的提示词能够包含之前的对话回合,从而在对话中保持连贯性。

简而言之,如果你是在构建一个需要持续多轮对话的系统,那么 ChatPromptTemplate 会是更合适的选择,因为它可以帮助你更好地管理对话历史和上下文。而如果你是在构建一个不需要持续对话或状态管理的文本生成系统,PromptTemplate 就足够了。

在比较复杂的聊天场景中我们还可以通过LangChain提供的不同类型的MessagePromptTemplate来灵活的处理,最常用的是AIMessagePromptTemplate、 SystemMessagePromptTemplate和HumanMessagePromptTemplate,分别创建人工智能消息、系统消息和人工消息。比如上面的案例我们修改为

# 导入聊天消息类模板
from langchain.prompts import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)

# 模板的构建
system_template = "你是一个翻译专家,擅长将 {input_language} 语言翻译成 {output_language}语言."
system_message_prompt = SystemMessagePromptTemplate.from_template(system_template)

human_template = "{text}"
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)

prompt_template = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])

# 格式化提示消息生成提示
prompt = prompt_template.format_prompt(input_language="英文", output_language="中文",
                                       text="I love Large Language Model.").to_messages()
# 打印模版
print("prompt:", prompt)
from langchain_openai import ChatOpenAI

# 创建模型实例
model = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
)
# 得到模型的输出
result = model.invoke(prompt)
# 打印输出内容
print("result:", result)

对于这种拆分大家可以不太清楚应该要怎么来选择,直接使用 ChatPromptTemplate 与拆分为 SystemMessagePromptTemplateHumanMessagePromptTemplate 的区别如下:

  • 直接性:使用 ChatPromptTemplate 可以更直接地定义整个对话流程,而拆分模板则需要分别定义系统消息和用户消息,然后组合它们。

  • 灵活性:拆分模板提供了更高的灵活性,允许更精细地控制对话的不同部分。例如,你可能想要在对话的不同阶段改变系统消息,或者根据不同的用户输入使用不同的模板。

  • 可读性和维护性:拆分模板可能使得代码更加模块化,从而提高可读性和维护性。每个模板都有明确的职责,这有助于在未来修改或扩展对话系统时更容易理解和修改。

  • 复杂性:直接使用 ChatPromptTemplate 可能会更简单,特别是对于简单的对话场景。然而,对于复杂的对话系统,拆分模板可能更有助于管理复杂性。

少量样本示例

少量样本示例的提示模板,基于LLM模型与聊天模型,可分别使用FewShotPromptTemplate或FewShotChatMessagePromptTemplate,两者使用基本一致。

创建示例集:创建一些提示样本,每个示例都是一个字典,其中键是输入变量,值是输入变量的值

examples = [
    {"input": "2+2", "output": "4", "description": "加法运算"},
    {"input": "5-2", "output": "3", "description": "减法运算"},
]

创建提示模板,结合样本实例来生成对应的提示词信息。

from langchain.prompts import PromptTemplate

examples = [
    {"input": "2+2", "output": "4", "description": "加法运算"},
    {"input": "5-2", "output": "3", "description": "减法运算"},
]

# 创建提示模板,配置一个提示模板,将一个示例格式化为字符串
prompt_template = "你是一个数学专家,算式: {input} 值: {output} 使用: {description} "

# 这是一个提示模板,用于设置每个示例的格式
prompt_sample = PromptTemplate.from_template(prompt_template)

# input="4+2", output="6", description="加法运算"
print(prompt_sample.format_prompt(**examples[0]))
#print(prompt_sample.format_prompt(input="2+2", output="4", description="加法运算"))

创建FewShotPromptTemplate对象

from langchain.prompts import PromptTemplate

examples = [
    {"input": "2+2", "output": "4", "description": "加法运算"},
    {"input": "5-2", "output": "3", "description": "减法运算"},
]

# 创建提示模板,配置一个提示模板,将一个示例格式化为字符串
prompt_template = "你是一个数学专家,算式: {input} 值: {output} 使用: {description} "

# 这是一个提示模板,用于设置每个示例的格式
prompt_sample = PromptTemplate.from_template(prompt_template)


# 创建一个FewShotPromptTemplate对象
from langchain.prompts.few_shot import FewShotPromptTemplate

prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=prompt_sample,
    suffix="你是一个数学专家,算式: {input}  值: {output}",
    input_variables=["input", "output"]
)
print(prompt.format(input="2*5", output="10"))  # 你是一个数学专家,算式: 2*5  值: 10

输出的结果:

你是一个数学专家,算式: 2+2 值: 4 使用: 加法运算 

你是一个数学专家,算式: 5-2 值: 3 使用: 减法运算 

你是一个数学专家,算式: 2*5  值: 10

初始化大模型,然后调用:

from dotenv import load_dotenv
load_dotenv()
from langchain.prompts import PromptTemplate

examples = [
    {"input": "2+2", "output": "4", "description": "加法运算"},
    {"input": "5-2", "output": "3", "description": "减法运算"},
]

# 创建提示模板,配置一个提示模板,将一个示例格式化为字符串
prompt_template = "你是一个数学专家,算式: {input} 值: {output} 使用: {description} "

# 这是一个提示模板,用于设置每个示例的格式
prompt_sample = PromptTemplate.from_template(prompt_template)


# 创建一个FewShotPromptTemplate对象
from langchain.prompts.few_shot import FewShotPromptTemplate

prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=prompt_sample,
    suffix="你是一个数学专家,算式: {input}  值: {output}",
    input_variables=["input", "output"]
)
print(prompt.format(input="2*5", output="10"))  # 你是一个数学专家,算式: 2*5  值: 10

from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
)
result = model.invoke(prompt.format(input="2*5", output="10"))
print(result.content)  # 使用: 乘法运算

三、LangChain之Chain链

为开发更复杂的应用程序,需要使用Chain来链接LangChain中的各个组件和功能,包括模型之间的链接以及模型与其他组件之间的链接

链在内部把一系列的功能进行封装,而链的外部则又可以组合串联。 链其实可以被视为LangChain中的一种基本功能单元。

API地址:chains — 🦜🔗 LangChain documentation

3.1 链的基本使用

LLMChain是最基础也是最常见的链。LLMChain结合了语言模型推理功能,并添加了PromptTemplate和Output Parser等功能,将模型输入输出整合在一个链中操作。

它利用提示模板格式化输入,将格式化后的字符串传递给LLM模型,并返回LLM的输出。这样使得整个处理过程更加高效和便捷。

未使用Chain链的情况:

# 导入LangChain中的提示模板
from langchain_core.prompts import PromptTemplate

# 原始字符串模板
template = "桌上有{number}个苹果,四个桃子和 3 本书,一共有几个水果?"

# 创建LangChain模板
prompt_temp = PromptTemplate.from_template(template)

# 根据模板创建提示
prompt = prompt_temp.format(number=2)

# 导入LangChain中的OpenAI模型接口
from langchain_openai import ChatOpenAI

# 创建模型实例
model = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
    temperature=0
)
# 传入提示,调用模型返回结果
result = model.invoke(prompt)
print(result)

使用Chain链的情况:

from langchain.chains.llm import LLMChain
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI

# 原始字符串模板
template = "桌上有{number}个苹果,四个桃子和 3 本书,一共有几个水果?"

# 创建模型实例
llm = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
    temperature=0
)

# 创建LLMChain
llm_chain = LLMChain(
    llm=llm,
    prompt=PromptTemplate.from_template(template)
)

# 调用LLMChain,返回结果
result = llm_chain.invoke({"number":2})
print(result)

3.2 使用表达式语言 (LCEL)

LangChain表达式语言,或 LCEL,是一种声明式的方法,可以轻松地将链组合在一起。LCEL 从第一天开始就被设计为支持将原型投入生产,无需更改代码,从最简单的“prompt + LLM”链到最复杂的链(我们已经看到有人在生产中成功运行了包含 100 多个步骤的 LCEL 链)。

from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI

# 原始字符串模板
template = "桌上有{number}个苹果,四个桃子和 3 本书,一共有几个水果?"
prompt = PromptTemplate.from_template(template)

# 创建模型实例
llm = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
    temperature=0
)

# 创建Chain  模版 -> 大模型 -> outparse
chain = prompt | llm

# 调用Chain,返回结果
result = chain.invoke({"number": "3"})
print(result)

3.3 案例演示

用LangChain写Python代码并执行来生成答案。这块需要单独的添加下依赖

pip install langchain_experimental -i https://pypi.tuna.tsinghua.edu.cn/simple
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import (
    ChatPromptTemplate,
)
from langchain_experimental.utilities import PythonREPL
from langchain_openai import ChatOpenAI

template = """Write some python code to solve the user's problem.

Return only python code in Markdown format, e.g.:

```python
....
```"""
prompt = ChatPromptTemplate.from_messages([("system", template), ("human", "{input}")])

model = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
)

def _sanitize_output(text: str):
    _, after = text.split("```python")
    return after.split("```")[0]

# PythonREPL().run 就是调用了一下 exec 函数执行代码
chain = prompt | model | StrOutputParser() | _sanitize_output | PythonREPL().run
# chain = prompt | model | StrOutputParser()
result = chain.invoke({"input": "whats 2 plus 2"})

print(result)

3.4 链的调用

invoke方法

invoke是Chain的主要调用方法,用于执行整个Chain流程。它会处理输入,执行Chain中的所有步骤,并返回最终结果。

from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI

# 原始字符串模板
template = "桌上有{number}个苹果,四个桃子和 3 本书,一共有几个水果?"
prompt = PromptTemplate.from_template(template)

# 创建模型实例
llm = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
    temperature=0
)

# 创建Chain
chain = prompt | llm

# 调用Chain,返回结果
result = chain.invoke({"number": "3"})
print(result)

predict方法

predict通常用于获取模型的预测结果,可能会跳过Chain中的某些步骤,比如输入预处理或后处理。它专注于模型的预测部分,而不是整个Chain的流程。

通过predict方法,将输入键指定为关键字参数

from langchain.chains.llm import LLMChain
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI

# 创建模型实例
template = "桌上有{number}个苹果,四个桃子和 3 本书,一共有几个水果?"
prompt = PromptTemplate(template=template, input_variables=["number"])

# 创建LLM
llm = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
    temperature=0
)
# 创建LLMChain
llm_chain = LLMChain(llm=llm, prompt=prompt)
# llm_chain = prompt | llm
# 调用LLMChain,返回结果
result = llm_chain.predict(number=3)
print(result)

batch 方法

通过batch方法(原apply方法):batch方法允许输入列表运行链,一次处理多个输入。

from dotenv import load_dotenv
load_dotenv()
from langchain.chains.llm import LLMChain
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI

# 创建模型实例
template = PromptTemplate(
    input_variables=["role", "fruit"],
    template="{role}喜欢吃{fruit}?",
)

# 创建LLM
llm = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
    temperature=0
)

# 创建LLMChain
llm_chain = LLMChain(llm=llm, prompt=template)

# 输入列表
input_list = [
    {"role": "猪八戒", "fruit": "人参果"}, {"role": "孙悟空", "fruit": "仙桃"}
]

# 调用LLMChain,返回结果
result = llm_chain.batch(input_list)
print(result)

3.5 官方工具链

官方提供了很多封装好的文档链,我们可以直接拿过来直接使用的。

chains — 🦜🔗 LangChain documentation

文档链

create_stuff_documents_chain链将获取文档列表并将它们全部格式化为提示(文档列表),然后将该提示传递给LLM。

from langchain_openai import ChatOpenAI
from langchain_core.documents import Document
from langchain_core.prompts import ChatPromptTemplate
from langchain.chains.combine_documents import create_stuff_documents_chain
import bs4
from langchain_community.document_loaders import WebBaseLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter


# 创建提示模板
prompt = ChatPromptTemplate.from_messages(
    [("system", """根据提供的上下文: {context} \n\n 回答问题: {input}""")]
)

# 初始化大模型
llm = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
)

# 构建链  这个链将文档作为输入,并使用之前定义的提示模板和初始化的大模型来生成答案
chain = create_stuff_documents_chain(llm, prompt)

# 加载文档
loader = WebBaseLoader(
        web_path="https://www.gov.cn/xinwen/2020-06/01/content_5516649.htm",
        bs_kwargs=dict(parse_only=bs4.SoupStrainer(id="UCAP-CONTENT"))
        )

docs = loader.load()

# 分割文档
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=50)
documents = text_splitter.split_documents(docs)
# print(documents)
print(len(documents))

# 执行链  检索  民事法律行为? 出来的结果
res = chain.invoke({"input": "民事法律行为?", "context": documents[:5]})
print(res)

 

数学链

LLMMathChain将用户问题转换为数学问题,然后将数学问题转换为可以使用 Python 的 numexpr 库执行的表达式。使用运行此代码的输出来回答问题

# 使用LLMMathChain,需要安装numexpr库
pip install numexpr

from dotenv import load_dotenv
load_dotenv()
from langchain_openai import ChatOpenAI
from langchain.chains import LLMMathChain

# 初始化大模型
llm = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
)

# 创建链
llm_math = LLMMathChain.from_llm(llm)

# 执行链
res = llm_math.invoke("10 ** 3 + 100的结果是多少?")
print(res)

SQL查询链

create_sql_query_chain是创建生成SQL查询的链,用于将自然语言转换成数据库的SQL查询。

# 这里使用MySQL数据库,需要安装pymysql
pip install pymysql

先来看下如何通过Python获取数据库的连接信息

from langchain_community.utilities import SQLDatabase

# 连接 sqlite 数据库
# db = SQLDatabase.from_uri("sqlite:///demo.db")

# 连接 MySQL 数据库
db_user = "root"
db_password = "123456"
db_host = "127.0.0.1"
db_port = "3306"
db_name = "llm"
db = SQLDatabase.from_uri(f"mysql+pymysql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}")

print("那种数据库:",db.dialect)
print("获取数据表:",db.get_usable_table_names())
# 执行查询
res = db.run("SELECT count(*) FROM students;")
print("查询结果:",res)

然后结合SQL查询链来生存对应的SQL语句

from langchain_community.utilities import SQLDatabase

# 连接 sqlite 数据库
# db = SQLDatabase.from_uri("sqlite:///demo.db")

# 连接 MySQL 数据库
db_user = "root"
db_password = "123456"
db_host = "127.0.0.1"
db_port = "3306"
db_name = "llm"
db = SQLDatabase.from_uri(f"mysql+pymysql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}")

from langchain_openai import ChatOpenAI
from langchain.chains import create_sql_query_chain
# 初始化大模型
llm = ChatOpenAI(
    api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",  # 替换为你的 API Key
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 阿里云 DashScope 的兼容模式地址
    model_name="qwen-plus",  # 替换为你需要使用的模型名称
    temperature=0
)

chain = create_sql_query_chain(llm=llm, db=db)


#response = chain.invoke({"question": "查询考试科目一共有多少?"})
response = chain.invoke({"question": "查询一班的学生数学成绩是多少?"})
# 限制使用的表
# response = chain.invoke({"question": "一共有多少个学生?", "table_names_to_use": ["students"]})
print(response)

四、LangChain之RAG

LangChain为RAG应用程序提供了从简单到复杂的所有构建块,本文要学习的数据连接(Retrieval)模块包括与检索步骤相关的所有内容,例如数据的获取、切分、向量化、向量存储、向量检索等模块.

4.1 文档加载

LangChain封装了一系列类型的文档加载模块,例如PDF、CSV、HTML、JSON、Markdown、File Directory等。下面以PDF文件夹在为例看一下用法,其它类型的文档加载的用法都类似。

使用指南 | LangChain中文网

我们以pdf为例来介绍下,这里需要安装下 pypdf 这个组件,

from langchain_community.document_loaders import PyPDFLoader
from dotenv import load_dotenv
load_dotenv()

loader = PyPDFLoader("llama2.pdf")
pages = loader.load_and_split()

print(f"第0页:\n{pages[0]}") ## 也可通过 pages[0].page_content只获取本页内容

4.2 文档切割

LangChain提供了许多不同类型的文本切分器

官网地址:langchain-text-splitters: 0.3.7 — 🦜🔗 LangChain documentation

这里以Recursive为例展示用法。RecursiveCharacterTextSplitter是LangChain对这种文档切分方式的封装,里面的几个重点参数

  • chunk_size:每个切块的token数量

  • chunk_overlap:相邻两个切块之间重复的token数量

from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader("./llama2.pdf")
pages = loader.load_and_split()
print(f"第0页:\n{pages[0].page_content}")

from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=200,
    chunk_overlap=100,
    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('-------')

这里提供了一个可视化展示文本如何分割的工具,ChunkViz

4.3 文本向量化

LangChain对一些文本向量化模型的接口做了封装,例如OpenAI, Cohere, Hugging Face等。 向量化模型的封装提供了两种接口,一种针对文档的向量化embed_documents,一种针对句子的向量化embed_query。

文档的向量化embed_documents,接收的参数是字符串数组

from langchain_openai import OpenAIEmbeddings
from dotenv import load_dotenv
load_dotenv()
embeddings_model = OpenAIEmbeddings()  ## OpenAI文本向量化模型接口的封装

embeddings = embeddings_model.embed_documents(
    [
        "Hi there!",
        "Oh, hello!",
        "What's your name?",
        "My friends call me World",
        "Hello World!"
    ]
)

print(len(embeddings), len(embeddings[0]))
print(embeddings[0][:5])
##运行结果 (5, 1536)

句子的向量化embed_query,接收的参数是字符串

from langchain_openai import OpenAIEmbeddings
from dotenv import load_dotenv
load_dotenv()
embeddings_model = OpenAIEmbeddings()  ## OpenAI文本向量化模型接口的封装

embedded_query = embeddings_model.embed_query("What was the name mentioned in the conversation?")
print(embedded_query[:5])

4.4 向量存储

将文本向量化之后,下一步就是进行向量的存储。

这部分包含两块:一是向量的存储。二是向量的查询。

官方提供了三种开源、免费的可用于本地机器的向量数据库示例(chroma、FAISS、 Lance)

安装模块:pip install chromadb

创建向量数据库,灌入数据

from langchain_openai import OpenAIEmbeddings
from langchain_community.document_loaders import PyPDFLoader
from dotenv import load_dotenv
load_dotenv()
loader = PyPDFLoader("./llama2.pdf")
pages = loader.load_and_split()

## 2. 文档切分
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=200,
    chunk_overlap=100,
    length_function=len,
    add_start_index=True,
)
paragraphs = []
for page in pages:
    paragraphs.extend(text_splitter.create_documents([page.page_content]))

# print(paragraphs)
## 3. 文档向量化,向量数据库存储
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
db = Chroma.from_documents(paragraphs, OpenAIEmbeddings())

查询similarity_search

query = "llama2有多少参数?"
docs = db.similarity_search(query) ## 一行代码搞定
for doc in docs:
    print(f"{doc.page_content}\n-------\n")

4.5 检索器

检索器是在给定非结构化查询的情况下返回相关文本的接口。它比Vector stores更通用。检索器不需要能够存储文档,只需要返回(或检索)文档即可。Vector stores可以用作检索器的主干,但也有其他类型的检索器。检索器接受字符串查询作为输入,并返回文档列表作为输出

retrievers — 🦜🔗 LangChain documentation

名称索引类型使用LLM何时使用描述
VectorstoreVectorstore刚开始接触,想快速简单的入门这是 最简单的方法,也是最容易入门的方法。它涉及为每个文本片段创建向量。
ParentDocumentVectorstore + 文档存储如果您的页面有许多较小的独立信息片段,最好单独索引,但最好一起检索。这涉及为每个文档索引多个片段。然后找到在嵌入空间中最相似的片段,但检索整个父文档并返回(而不是单个片段)。
Multi VectorVectorstore + 文档存储有时在索引过程中如果您能够从文档中提取比文本本身更相关的信息。这涉及为每个文档创建多个向量。每个向量可以以多种方式创建 - 例如,文本摘要和假设性问题。
Self QueryVectorstore如果用户提出的问题更适合根据元数据而不是文本相似性来获取文档回答。这使用LLM将用户输入转换为两件事:(1)语义查找的字符串,(2)与之配套的元数据过滤器。
Contextual Compression任意有时如果您发现检索到的文档包含太多不相关信息,并且干扰了LLM。这在另一个检索器之上放置了后处理步骤,并仅从检索到的文档中提取最相关的信息。可以使用嵌入或LLM完成。
Time-Weighted VectorstoreVectorstore如果您的文档有时间戳,并且您想检索最近的文档。这根据语义相似性(与常规向量检索相同)和时间权重(查看索引文档的时间戳)检索文档。
Multi-Query Retriever任意如果用户提出的问题复杂,并且需要多个独立信息片段来回应。这使用LLM从原始查询生成多个查询。当原始查询需要有关多个主题的信息片段才能正确回答时,这很有用。
Ensemble任意如果您有多个检索方法并希望尝试将它们组合起来。这从多个检索器中获取文档,然后将它们组合在一起。
Long-Context Reorder任意如果您使用长上下文模型,并且注意到它没有关注检索到的文档中的中间信息。这从基础检索器中获取文档,然后重新排序文档,使最相似的文档靠近开头和结尾。这很有用,因为长上下文模型有时不关注上下文窗口中间的信息。

以Vectorstore类型的检索为例简单使用

# as_retriever:生成检索器实例
retriever = db.as_retriever()
docs = retriever.invoke("llama2有多少参数?")
for doc in docs:
    print(f"{doc.page_content}\n-------\n")

指定一个相似度阈值为0.5,只有相似度超过这个值才会召回

retriever = db.as_retriever(
    search_type="similarity_score_threshold", search_kwargs={"score_threshold": 0.5}
)
docs = retriever.invoke("llama2有多少参数?")
print(docs)

指定检索几个文本片段:topK

retriever = db.as_retriever(search_kwargs={"k": 1})
docs = retriever.invoke("llama2有多少参数?")
print(docs)

最后完整的流程

## 1. 文档加载
from langchain_community.document_loaders import PyPDFLoader
from dotenv import load_dotenv
load_dotenv()


loader = PyPDFLoader("./llama2.pdf")
pages = loader.load_and_split()

## 2. 文档切分
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=200,
    chunk_overlap=100,
    length_function=len,
    add_start_index=True,
)
paragraphs = []
for page in pages:
    paragraphs.extend(text_splitter.create_documents([page.page_content]))

# print(paragraphs)
## 3. 文档向量化,向量数据库存储
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
db = Chroma.from_documents(paragraphs, OpenAIEmbeddings())

## 4. 向量检索
retriever = db.as_retriever()
docs = retriever.invoke("llama2有多少参数?")
# for doc in docs:
    # print(f"{doc.page_content}\n-------\n")

## 5. 组装Prompt模板
import os
# 加载 .env 到环境变量
from dotenv import load_dotenv
load_dotenv()

from langchain_openai import ChatOpenAI

llm = ChatOpenAI() # 默认是gpt-3.5-turbo

prompt_template = """
你是一个问答机器人。
你的任务是根据下述给定的已知信息回答用户问题。
确保你的回复完全依据下述已知信息。不要编造答案。
如果下述已知信息不足以回答用户的问题,请直接回复"我无法回答您的问题"。

已知信息:
{info}

用户问:
{question}

请用中文回答用户问题。
"""

from langchain.prompts import PromptTemplate

template = PromptTemplate.from_template(prompt_template)

prompt = template.format(info=docs[2].page_content, question='llama2有多少参数?')
print(prompt)
## 6. 调用LLM
response = llm.invoke(prompt)
print(response.content)  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Calvad0s

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值