LangChain 介绍
LangChain是一个用于开发由大型语言模型(LLM)支持的应用程序的框架。
LangChain简化了LLM申请生命周期的每个阶段:
- 开发:使用LangChain的开源构建块和组件构建您的应用程序。使用第三方集成和模板开始运行。
- 生产化:使用LangSmith检查、监控和评估您的链,以便您可以充满信心地持续优化和部署。
- 部署:使用LangServe将任何链变成 API 。
具体来说,该框架由以下开源库组成:
langchain-core
:基础抽象和LangChain表达式语言。langchain-community
:第三方集成。- 合作的包(例如
langchain-openai
,,langchain-anthropic
等):一些集成已被进一步拆分为自己的轻量级包,仅依赖于langchain-core
.
- 合作的包(例如
langchain
:构成应用程序认知架构的链、代理和检索策略。- langgraph:通过将步骤建模为图中的边和节点,使用 LLM 构建健壮且有状态的多角色应用程序。
- langserve:将 LangChain 链部署为 REST API。
更广泛的生态系统包括:
- LangSmith:一个开发者平台,可让您调试、测试、评估和监控LLM应用程序,并与LangChain无缝集成。
应用场景
- 1.信息检索:可以构建简单的搜索引擎,基于文本的相似度,匹配最相似的(可添加自定义条件)
- 2.提取结构化输出: 主要是针对复杂的文本内容,做结构化的内容提取,指定输出 schema (甚至包括对结果的自定义计算)
- 3.RAG 问答:主要是针对内容检索和答案生成(RAG全称为Retrieval Augmented Generation,检索增强生成)
- 3.SQL+CSV问答:指定SQL-chain/CSV-tool,对文本进行解析、转化为SQL/Pandas,并返回生成的问题结果。
- 4.聊天机器人:主要是针对问题问答、和上下文结合优化反馈结果
- 5.文本摘要:对文本内容 - 全局、分块、精细向量化、然后生成我们需要的结果
RAG问答 和 聊天机器人的 区别和联系
区别:
- RAG问答系统是一种结合了信息检索(Retrieval)和生成(Generation)的问答系统。
- RAG系统通常使用预训练的语言模型(如BERT、GPT)作为生成模型,并结合检索模型(如BM25、TF-IDF)进行信息检索。
- 例如:信息检索和答案的生成
- 聊天机器人是一种通过对话形式与用户进行交互的人工智能系统。
- 聊天机器人通常用于回答用户提出的各种问题、提供信息、执行任务等。
- 例如:对上下文的结合,影响到反馈的结果
联系:
- 聊天机器人,如果不结合上下文,本质上也是一个RAG的问答系统。
- 当聊天机器人没找到答案的时候,可以通过提供额外数据,调用RAG系统进行更深入的信息检索和生成回答。
LLM 介绍
快速入门
在本快速入门中,我们将向您展示如何:
- 使用 LangChain、LangSmith 和 LangServe 进行设置
- 使用LangChain最基本、最常用的组件:提示模板、模型和输出解析器
- 使用 LangChain 表达式语言,这是 LangChain 构建的协议,有助于组件链接
- 使用LangChain构建一个简单的应用程序
- 使用 LangSmith 追踪您的应用程序
- 使用 LangServe 为您的应用程序提供服务
这是一个相当大的篇幅!让我们深入了解一下。
LLM 组成成分(Components)
- 1.Model I/O: 格式化和管理语言模型输入和输出
- Prompts: 指导生成的 LLM 输入格式,
- Promps可以是单个句子或多个句子的组合,它们可以包含变量和条件语句
- Chat models: 多平台的聊天模型,相当于集成好的标准接口模块
- Cache 缓存
- 优点:1.减少您对 LLM 提供商的 API 调用次数来节省资 2.减少您对 LLM 提供商的 API 调用次数来加快您的申请速度。
- 缓存方式:1.In Memory Cache 2.SQLite Cache
- tools:
- 例如,如果您想从非结构化文本中提取与某些模式匹配的输出 ,您可以给模型一个“提取”工具,它采用与所需模式匹配的参数,然后将生成的输出视为最终结果。
- Prompts: 通过添加 examples 案例,纠正模型在使用的时候解析错误。
- Cache 缓存
- LLMs: LLM的装饰器,标准化格式,方便用户自定义自己的模型接口
- Output Parsers: 输出解析器,用于结构化输出结果
- Pydantic 自定义结构化对象(还有其他的。。)
- model.with_structured_output(schema=Obj) 也行
- Prompts: 指导生成的 LLM 输入格式,
- 2.Retrieval(检索): 与 RAG 等特定应用数据的接口
- Document loaders: 文档加载器
- Text splitters: 文档分割器
- Embedding models: 嵌入模型
- 支持 Local/Memory 缓存,加速
- Vectorstores: 文本的矢量表示
- FAISS/Chroma 矢量数据库
- Retrievers: 文本检索的匹配规则和匹配数量
- 基于距离:最大边际相关性搜索 mmr / 相似度分数阈值,并且仅返回分数高于该阈值的 / 指定相似度最接近的N个
- 基于多视角:通过对同一问题产生多个视角,MultiQueryRetriever 可能能够克服基于距离的检索的一些限制并获得更丰富的结果集。
- 压缩上下文:使用给定查询的上下文来压缩它们,以便只返回相关信息,而不是立即按原样返回检索到的文档。
- Indexing: 用于索引的存储记录
- 3.Composition: 用于与LangChain原生进行组合的高级组件
- Tools: 标准化定义的工具模块,用于构建工具!方便 Agent 和 Chains 两个随时来调用
- Agents: 代理,封装了 llm+tools+prompt,而生成agent客户端可以开放其他人反复使用
- 是一种使用LLM做出决策的工具,它们可以执行特定的任务并生成文本输出。
- Chains: 类似:管道,允许自定义的指定链路执行顺序
- 是一种将LLM和其他多个组件连接在一起的工具,以实现复杂的任务。
- 4.Additional: 更多内容
- Memory: 可以保留记忆,上下文分析推理
- 是一种用于存储数据的工具,由于LLM 没有任何长期记忆,它有助于在多次调用之间保持状态。
- 内存 -> chat_history
- redis数据库 -> RedisChatMessageHistory
- 定制对话角色名称 - AI 和 Human
- Callbacks: 回调系统,允许您连接到 LLM 申请的各个阶段。这对于日志记录、监控、流传输和其他任务非常有用。
- Memory: 可以保留记忆,上下文分析推理
用户可以与多个Agent Executor进行交互,而每个Agent Executor负责管理一个具体的Agent,并执行该Agent的任务
简单补充,部署是: - langserve + FastAPI,完全服务相互调用 - 当然也可以用 flask/django 等,自己配置调用
Tools 的使用方式主要有两种: chain 和 agent
LLM 的一个令人兴奋的用例是为其他“tools”构建自然语言接口,无论是 API、函数、数据库等。
LangChain 非常适合构建此类接口,因为它具有:
- 良好的模型输出解析,可以轻松从模型输出中提取 JSON、XML、OpenAI 函数调用等。
- 大量内置 “tools”。
- 为您如何调用这些工具提供了很大的灵活性。
chain(链): 允许您创建预定义的工具使用顺序(类似:管道)
agent(代理): 让模型循环使用工具,这样它就可以决定使用工具的次数
实战案例
安装和配置
pip install langchain
您使用 LangChain 构建的许多应用程序将包含多个步骤以及多次调用 LLM 调用。
随着这些应用程序变得越来越复杂,能够检查链或代理内部到底发生了什么变得至关重要。最好的方法是使用LangSmith。
LangSmith 不是必需的,但它很有帮助。
如果您确实想使用 LangSmith,请在上面的链接注册后,确保设置环境变量以开始记录跟踪:
export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY="..."
我们将展示如何使用通过 API 提供的模型(例如 OpenAI)和本地开源模型,以及使用 Ollama 等集成。
pip install langchain-openai
访问 API 需要 OpenAI 的密钥
export OPENAI_API_KEY="..."
LLM模型通过调用openai进行提问
from langchain_openai import ChatOpenAI
# llm = ChatOpenAI()
# 如何不像配置 OPENAI_API_KEY 的环境变量,可以直接写到代码中
llm = ChatOpenAI(api_key="...")
# 提问
llm.invoke("how can langsmith help with testing?")
# 自定义 提示模版 prompt
# prompt 就是你给模型的提示或者指导,告诉模型应该做什么或者回答什么。
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "You are world class technical documentation writer."),
("user", "{input}")
])
# 将它们组合成一个简单的 LLM 链,再次进行提问
chain = prompt | llm
chain.invoke({"input": "how can langsmith help with testing?"})
# 让我们添加一个简单的输出解析器来将聊天消息转换为字符串。
from langchain_core.output_parsers import StrOutputParser
output_parser = StrOutputParser()
chain = prompt | llm | output_parser
# 现在答案将是一个字符串(而不是 ChatMessage)
chain.invoke({"input": "how can langsmith help with testing?"})
通过外部数据完善LLM模型的回答
安装爬虫库
pip install beautifulsoup4 # 爬虫
pip install faiss-cpu # 本地向量库
爬取外部数据
# 加载外部数据
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://docs.smith.langchain.com/user_guide")
docs = loader.load()
# 使用嵌入模型将文档提取到向量存储中
from langchain_openai import OpenAIEmbeddings
# embeddings = OpenAIEmbeddings()
embeddings = OpenAIEmbeddings(api_key="...")
# 建立我们的索引 - 我们已经在矢量存储中索引了这些数据,我们将创建一个检索链
# 该链将接受传入的问题,查找相关文档,然后将这些文档与原始问题一起传递给 LLM 并要求其回答原始问题。
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter()
documents = text_splitter.split_documents(docs)
vector = FAISS.from_documents(documents, embeddings)
## 首先,让我们建立一个链,该链接受问题和检索到的文档并生成答案。
from langchain.chains.combine_documents import create_stuff_documents_chain
prompt = ChatPromptTemplate.from_template("""Answer the following question based only on the provided context:
<context>
{context}
</context>
Question: {input}""")
document_chain = create_stuff_documents_chain(llm, prompt)
# # 可以通过直接传入文档来运行它
# # 例如我的文档内容: [Document(page_content="langsmith can let you visualize test results")]
# from langchain_core.documents import Document
# document_chain.invoke({
# "input": "how can langsmith help with testing?",
# "context": [Document(page_content="langsmith can let you visualize test results")]
# })
# 但是,我们希望文档首先来自我们刚刚设置的检索器。这样,我们可以使用检索器动态选择最相关的文档并将其传递给给定的问题。
from langchain.chains import create_retrieval_chain
retriever = vector.as_retriever()
retrieval_chain = create_retrieval_chain(retriever, document_chain)
response = retrieval_chain.invoke({"input": "how can langsmith help with testing?"})
print(response["answer"])
# LangSmith offers several features that can help with testing:...
如何更新对话索引
到目前为止,我们创建的链只能回答单个问题。人们构建的 LLM 应用程序的主要类型之一是聊天机器人。那么我们如何将这个链条变成一个可以回答后续问题的链条呢?
我们仍然可以使用该create_retrieval_chain
函数,但我们需要更改两件事:
- 检索方法现在不应仅适用于最近的输入,而应考虑整个历史记录。
- 最终的 LLM 链同样应该考虑整个历史
from langchain.chains import create_history_aware_retriever
from langchain_core.prompts import MessagesPlaceholder
# First we need a prompt that we can pass into an LLM to generate this search query
prompt = ChatPromptTemplate.from_messages([
MessagesPlaceholder(variable_name="chat_history"),
("user", "{input}"),
("user", "Given the above conversation, generate a search query to look up to get information relevant to the conversation")
])
retriever_chain = create_history_aware_retriever(llm, retriever, prompt)
# 我们可以通过传递用户提出后续问题的实例来测试这一点。
from langchain_core.messages import HumanMessage, AIMessage
chat_history = [HumanMessage(content="Can LangSmith help test my LLM applications?"), AIMessage(content="Yes!")]
retriever_chain.invoke({
"chat_history": chat_history,
"input": "Tell me how"
})
# 生成了一个新查询,将聊天历史记录与后续问题相结合。
# 现在我们有了这个新的检索器,我们可以创建一个新的链来继续与这些检索到的文档进行对话。
prompt = ChatPromptTemplate.from_messages([
("system", "Answer the user's questions based on the below context:\n\n{context}"),
MessagesPlaceholder(variable_name="chat_history"),
("user", "{input}"),
])
document_chain = create_stuff_documents_chain(llm, prompt)
# 将老 retriever_chain,生产一个新的 retrieval_chain 链
retrieval_chain = create_retrieval_chain(retriever_chain, document_chain)
chat_history = [HumanMessage(content="Can LangSmith help test my LLM applications?"), AIMessage(content="Yes!")]
retrieval_chain.invoke({
"chat_history": chat_history,
"input": "Tell me how"
})
我们可以看到,这给出了一个连贯的答案 - 我们已经成功地将检索链变成了聊天机器人!
如何创建代理 Agent
在本示例中,我们将仅展示如何使用 OpenAI 模型创建代理,因为本地模型还不够可靠。
构建代理时要做的第一件事就是决定它应该有权访问哪些工具。在此示例中,我们将授予代理访问两个工具的权限:
- 我们刚刚创建的检索器。这将让它轻松回答有关 LangSmith 的问题
- 一个搜索工具。这将使它能够轻松回答需要最新信息的问题。
# 创建的检索器设置一个工具
from langchain.tools.retriever import create_retriever_tool
retriever_tool = create_retriever_tool(
retriever,
"langsmith_search",
"Search for information about LangSmith. For any questions about LangSmith, you must use this tool!",
)
# 搜索工具是Tavily,需要配置 export TAVILY_API_KEY=...
# Tavily注册地址: https://python.langchain.com/docs/integrations/retrievers/tavily/
# 如果您不想设置 API 密钥,则可以跳过创建此工具。
from langchain_community.tools.tavily_search import TavilySearchResults
search = TavilySearchResults()
# 创建我们想要使用的工具的列表
tools = [retriever_tool, search]
# pip install langchainhub
# pip install langchain-openai
from langchain_openai import ChatOpenAI
from langchain import hub
from langchain.agents import create_openai_functions_agent
from langchain.agents import AgentExecutor
# Get the prompt to use - you can modify this!
prompt = hub.pull("hwchase17/openai-functions-agent")
# You need to set OPENAI_API_KEY environment variable or pass it as argument `api_key`.
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
agent = create_openai_functions_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
agent_executor.invoke({"input": "how can langsmith help with testing?"})
agent_executor.invoke({"input": "what is the weather in SF?"})
chat_history = [HumanMessage(content="Can LangSmith help test my LLM applications?"), AIMessage(content="Yes!")]
agent_executor.invoke({
"chat_history": chat_history,
"input": "Tell me how"
})
在 LangServe 运行
现在我们已经构建了一个应用程序,我们需要为它提供服务,这就是 LangServe 的用武之地。
LangServe 帮助开发人员将 LangChain 链部署为 REST API。您不需要使用 LangServe 来使用 LangChain,
但在本指南中,我们将向您展示如何使用 LangServe 部署您的应用程序。
# pip install "langserve[all]"
要为我们的应用程序创建服务器,我们将创建一个serve.py文件。这将包含我们为应用程序提供服务的逻辑。它由三部分组成:
- 我们刚刚在上面构建的链的定义
- 我们的 FastAPI 应用程序
- 为链提供服务的路由的定义,由以下命令完成
langserve.add_routes
Server 服务端
就是这样!如果我们执行这个文件:
python serve.py
我们应该看到我们的链在 localhost:8000
上提供服务。
每个 LangServe 服务都带有一个简单的内置 UI,用于配置和调用具有流输出和中间步骤可见性的应用程序。
前往 http://localhost:8000/agent/playground/
尝试一下!
#!/usr/bin/env python
from typing import List
from fastapi import FastAPI
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_community.document_loaders import WebBaseLoader
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.tools.retriever import create_retriever_tool
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain import hub
from langchain.agents import create_openai_functions_agent
from langchain.agents import AgentExecutor
from langchain.pydantic_v1 import BaseModel, Field
from langchain_core.messages import BaseMessage
from langserve import add_routes
# 1. Load Retriever
loader = WebBaseLoader("https://docs.smith.langchain.com/user_guide")
docs = loader.load()
text_splitter = RecursiveCharacterTextSplitter()
documents = text_splitter.split_documents(docs)
embeddings = OpenAIEmbeddings()
vector = FAISS.from_documents(documents, embeddings)
retriever = vector.as_retriever()
# 2. Create Tools
retriever_tool = create_retriever_tool(
retriever,
"langsmith_search",
"Search for information about LangSmith. For any questions about LangSmith, you must use this tool!",
)
search = TavilySearchResults()
tools = [retriever_tool, search]
# 3. Create Agent
prompt = hub.pull("hwchase17/openai-functions-agent")
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
agent = create_openai_functions_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# 4. App definition
app = FastAPI(
title="LangChain Server",
version="1.0",
description="A simple API server using LangChain's Runnable interfaces",
)
# 5. Adding chain route
# We need to add these input/output schemas because the current AgentExecutor
# is lacking in schemas.
class Input(BaseModel):
input: str
chat_history: List[BaseMessage] = Field(
...,
extra={"widget": {"type": "chat", "input": "location"}},
)
class Output(BaseModel):
output: str
add_routes(
app,
agent_executor.with_types(input_type=Input, output_type=Output),
path="/agent",
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="localhost", port=8000)
Client 客户端
也可以直接通过客户端直接请求
from langserve import RemoteRunnable
remote_chain = RemoteRunnable("http://localhost:8000/agent/")
remote_chain.invoke({
"input": "how can langsmith help with testing?",
"chat_history": [] # Providing an empty list as this is the first call
})