Langchain文档-起步

2023-09-19

使用 LangChain 构建

LangChain 使构建连接外部数据源和计算到LLMs的应用程序变得更加容易。在这个快速入门中,我们将演示几种不同的方式。我们将从一个简单的LLM链开始,它仅仅依赖于提示模板中的信息来作出响应。接下来,我们将构建一个检索链,它从一个独立的数据库中获取数据并将其传递到提示模板中。然后,我们将添加聊天记录,以创建一个对话检索链。这允许您以聊天的方式与LLM进行交互,以便它记住以前的问题。最后,我们将构建一个代理,它利用LLM来确定是否需要获取数据来回答问题。我们将以高层次的方式介绍这些内容,但这些都涉及许多细节!我们将链接到相关的文档。

LLM链

API使用

在这个入门指南中,我们将提供两个选项:使用阿里云模型(通过API可用的流行模型)或使用本地开源模型。

pip install langchain

访问API需要一个API密钥,您可以通过创建帐户并获取。一旦我们有了密钥,我们将希望将其设置为环境变量,通过运行以下命令:

export DASHSCOPE_API_KEY = "..."

或在python中使用os模块导入

import os
os.environ["DASHSCOPE_API_KEY"] = "..."

然后我们可以初始化模型:

from langchain.llms import Tongyi

llm = Tongyi()

如果您不想设置环境变量,可以在初始化 LLM类时直接通过_api_key命名参数传递密钥:

from langchain_ import Chat

llm = Chat(DASHSCOPE_API_KEY="...")

本地使用

Ollama 允许您在本地运行开源的大型语言模型,比如 Llama 2。

首先,请按照这些说明设置和运行本地的Ollama实例:

  • 下载
  • 通过 ollama pull llama2 获取模型

然后,确保Ollama服务器正在运行。之后,您可以执行以下操作:

from langchain_community.llms import Ollama
llm = Ollama(model="llama2")

调用模型

一旦您安装并初始化了所选LLM,我们可以尝试使用它!让我们问它LangSmith是什么 - 这是训练数据中没有的东西,所以它不应该有一个很好的回答。

llm.invoke("how can langsmith help with testing?")

模板调用

我们还可以使用提示模板来引导它的响应。提示模板用于将原始用户输入转换为更好的LLM输入。

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?"})

ChatModel的输出(因此,此链的输出)是一条消息。然而,使用字符串处理通常更方便。让我们添加一个简单的输出解析器,将聊天消息转换为字符串。

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?"})

检索链

为了正确回答原始问题(“langsmith如何帮助测试?”),我们需要向LLM提供额外的上下文。我们可以通过检索来实现这一点。当您有太多数据无法直接传递给LLM时,检索就变得很有用。您可以使用检索器仅获取最相关的片段并将其传递进去。

在此过程中,我们将从一个检索器中查找相关文档,然后将这些文档传递到提示中。检索器可以由任何东西支持 - SQL表、互联网等 - 但在这个示例中,我们将填充一个向量存储并将其用作检索器。

首先,我们需要加载要索引的数据。为此,我们将使用WebBaseLoader。这需要安装BeautifulSoup

pip install beautifulsoup4

之后,我们可以导入并使用WebBaseLoader。

from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://docs.smith.langchain.com/overview")

docs = loader.load()

接下来,我们需要将其索引到一个向量存储中。这需要一些组件,即嵌入模型和向量存储。

对于嵌入模型,我们再次提供了通过或通过本地模型访问的示例。

确保已安装 langchain 包并设置了适当的环境变量(这与LLM所需的相同)。

from langchain import Embeddings

embeddings = Embeddings()

确保Ollama正在运行(与LLM一样设置)。

from langchain_community.embeddings import OllamaEmbeddings

embeddings = OllamaEmbeddings()

现在,我们可以使用这个嵌入模型将文档加载到向量存储中。为了简单起见,我们将使用一个简单的本地向量存储FAISS

首先,我们需要安装该存储所需的软件包:

pip install faiss-cpu

然后我们可以构建我们的索引:

from langchain_community.vectorstores import FAISS
from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter()
documents = text_splitter.split_documents(docs)
vector = FAISS.from_documents(documents, embeddings)

现在我们已经在向量存储中索引了这些数据,我们将创建一个检索链。此链将获取传入的问题,查找相关文档,然后将这些文档连同原始问题一起传递到LLM中,并要求其回答原始问题。

首先,让我们设置一个链,该链接受一个问题和检索到的文档,并生成一个答案。

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)

如果我们愿意,我们可以自己运行它,直接传递文档:

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)

我们现在可以调用此链。这返回一个字典 - LLM的响应在answer键中。

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函数,但我们需要更改两件事:

  1. 检索方法现在不仅仅应该在最近的输入上工作,而是应该考虑整个历史记录。
  2. 最终的LLM链同样应该考虑整个历史记录

更新检索

为了更新检索,我们将创建一个新的链。这个链将接收最新的输入(input)和对话历史(chat_history),并使用LLM生成一个搜索查询。

from langchain.chains import create_history_aware_retriever
from langchain_core.prompts import MessagesPlaceholder

# 首先,我们需要一个我们可以传递到LLM以生成此搜索查询的提示

prompt = ChatPromptTemplate.from_messages([
    MessagesPlaceholder(variable_name="chat_history"),
    ("user", "{input}"),
    ("user", "Given the above conversation, generate a search query to look up in order 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"
})

你应该看到这返回了关于在LangSmith中测试的文档。这是因为LLM生成了一个新的查询,将聊天历史与后续问题结合起来。

现在我们有了这个新的检索器,我们可以创建一个新的链,以在考虑到这些检索到的文档的情况下继续对话。

prompt = ChatPromptTemplate.from_messages([
    ("system", "基于以下上下文回答用户的问题:\n\n{context}"),
    MessagesPlaceholder(variable_name="chat_history"),
    ("user", "{input}"),
])
document_chain = create_stuff_documents_chain(llm, prompt)

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"
})

我们可以看到这给出了一个连贯的答案 - 我们成功地将我们的检索链转化为一个聊天机器人!

代理

到目前为止,我们已经创建了链的示例 - 在这里,每个步骤都是预先知道的。 我们将创建的最后一件事是一个代理 - 在这里,LLM决定采取什么步骤。

注意:对于这个示例,我们将仅展示如何使用OpenAI模型创建代理,因为本地模型目前还不够可靠。

构建代理时的第一件事是决定它应该访问什么工具。 在这个例子中,我们将给代理访问两个工具的权限:

  1. 我们刚刚创建的检索器。这将使它轻松回答关于LangSmith的问题。
  2. 一个搜索工具。这将使它轻松回答需要最新信息的问题。

首先,让我们设置一个用于我们刚刚创建的检索器的工具:

from langchain.tools.retriever import create_retriever_tool

retriever_tool = create_retriever_tool(
    retriever,
    "langsmith_search",
    "搜索有关LangSmith的信息。有关LangSmith的任何问题,您必须使用此工具!",
)

现在,我们可以创建一个要使用的工具列表:

tools = [retriever_tool, search]

现在我们有了工具,可以创建一个代理来使用它们。我们将快速浏览一下这个过程

首先安装langchain hub

pip install langchainhub

现在我们可以使用它来获取预定义的提示

from langchain_openai import ChatOpenAI
from langchain import hub
from langchain.agents import create_openai_functions_agent
from langchain.agents import AgentExecutor

# 获取要使用的提示 - 您可以修改这个!
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)

现在我们可以调用代理并查看它的响应!我们可以问它关于LangSmith的问题:

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部署您的应用程序。

虽然本指南的第一部分是在Jupyter Notebook中运行的,但现在我们将离开那里。我们将创建一个Python文件,然后从命令行与其进行交互。

使用以下命令安装:

pip install "langserve[all]"

服务器

为了为我们的应用程序创建一个服务器,我们将制作一个serve.py文件。这将包含我们为提供应用程序而构建的逻辑。它包括三个部分:

  1. 我们刚刚构建的链的定义
  2. 我们的FastAPI应用程序
  3. 用于提供链的路由的定义,使用 langserve.add_routes 完成
#!/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_splitter import RecursiveCharacterTextSplitter
from langchain.tools.retriever import create_retriever_tool
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_openai import ChatOpenAI
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/overview")
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",
    "搜索有关LangSmith的信息。有关LangSmith的任何问题,您必须使用此工具!",
)
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)

到此为止!如果我们执行此文件:

python serve.py

Playground

每个LangServe服务都配有一个用于配置和调用应用程序的简单内置UI,具有流式输出和查看中间步骤的功能。 前往 http://localhost:8000/agent/playground/ 尝试一下!传入与之前相同的问题 - “how can langsmith help with testing?” - 它应该会像以前一样回答。

客户端

现在让我们为与服务进行程序化交互设置一个客户端。我们可以使用 langserve.RemoteRunnable 轻松实现这一点。 使用这个,我们可以与提供的链交互,就像它在客户端运行一样。

from langserve import RemoteRunnable

remote_chain = RemoteRunnable("http://localhost:8000/agent/")
remote_chain.invoke({"input": "how can langsmith help with testing?"})

下一步

我们已经涉及了如何使用LangChain构建应用程序,如何使用LangSmith对其进行跟踪,以及如何使用LangServe提供服务。 在这三个方面都有很多功能超出了我们在这里可以涵盖的范围。 为了继续您的旅程,我们建议您按照以下顺序阅读:

  • 所有这些功能都由 LangChain 表达语言 (LCEL) 支持 - 这是一种将这些组件链接在一起的方法。查看该文档以更好地了解如何创建自定义链。

  • 模型IO 包含有关提示、LLM和输出解析器的更多详细信息。

  • 检索 包含有关与检索相关的一切的更多详细信息

  • 代理 包含与代理相关的一切的详细信息 探索常见的 端到端用例 和 模板应用程序

  • 了解有关使用 LangServe 提供您的应用程序的更多信息