LangChain 快速入门

根据 LangChain 官网的 QuickStart 教程,在 Colab 中使用 Google Gemini API 学习 LangChain 基本操作。

这里选择使用 Google Gemini 是因为只需要一个 Google 账号就能免费使用 Gemini API,而获取 OpenAI Key 不管什么途径都比较麻烦且需要付费。

Installation

1
%pip install --upgrade --quiet langchain==0.1.20

以上命令会安装 LangChain 的最基本依赖。如果想要将 LangChain 与大模型、数据库等进行集成,还需要额外单独安装指定的依赖库。

Building with LangChain

LangChain 使我们能够构建一些应用,这些应用能够把外部的数据源和计算资源接入到大型语言模型(LLM)。在本快速入门教程中,我们会介绍几种实现这一点的不同方法。首先,我们将介绍一个简单的 LLM 链。这个链条仅仅依赖于提示模板中的信息来给出反馈。其次,我们会构建一个检索链,这个链会从一个独立的数据库获取数据,并将其传送到提示模板中。之后,我们将加入聊天历史,创建一个支持对话的检索链,让你能够以聊天的形式与 LLM 互动,并且 LLM 能够记住之前的提问。

LLM Chain

在这里使用 Google Gemini API,先安装 Google AI 的 Python 库:

1
%pip install --upgrade --quiet langchain-google-genai

从 Colab 中导入 Gemini API 密钥:

1
2
3
from google.colab import userdata

API_KEY = userdata.get('API_KEY')

之后让我们初始化模型,同时因为 gemini-1.5-pro-latest 仅允许每分钟 2 次请求,每天最多 1000 次请求,而 gemini-pro 允许每分钟 60 个请求,无查询总数限制,所以这里先用 gemini-pro实际上影响不大):

1
2
3
from langchain_google_genai import GoogleGenerativeAI

llm = GoogleGenerativeAI(model="gemini-pro", google_api_key=API_KEY)

我们来看看它对 LangSmith 是什么的解释 —— 这个概念在它的训练数据中不一定包含,因此我们可能不会得到一个很准确的答案。

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

输出

Test Case Generation:\n\nGenerate Test Cases Automatically:* Langsmith can generate test cases based on the specifications or code under test, reducing manual effort and ensuring thorough coverage.\n* Extract Test Cases from Code: It can extract test cases from existing code, helping to maintain test coverage and prevent regression bugs.\n\nTest Execution and Automation:\n\nExecute Tests Automatically:* Langsmith can automate test execution, saving time and ensuring consistent and repeatable results.\n* Integrate with CI/CD Pipelines: It can be integrated into CI/CD pipelines to run tests automatically as part of the build and deployment process.\n\nTest Reporting and Analysis:\n\nGenerate Test Reports:* Langsmith can generate detailed test reports that provide insights into test coverage, pass/fail rates, and any issues encountered.\n* Analyze Test Results: It can analyze test results to identify patterns, bottlenecks, and areas for improvement i…’

我们还可以使用提示模板来指导它给出回答。这些提示模板能够将用户的初始输入优化成更适合 LLM 的输入形式。

1
2
3
4
5
6
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
("system", "You are a world class technical documentation writer."),
("user", "{input}")
])

现在我们可以将这些组合成一个简单的 LLM 链。

1
chain = prompt | llm

我们现在可以调用它并询问同样的问题。尽管它依然不知道答案,但它回复的语气应该更贴近技术撰稿人的口吻。

1
chain.invoke({"input": "how can langsmith help with testing?"})

输出

Test Case Generation\n\nNatural language processing (NLP): Langsmith can extract test cases from user stories, requirements documents, and other text-based sources.\n Machine learning (ML): Langsmith’s ML algorithms can automatically generate test cases based on predefined rules and patterns.\n\nTest Case Management\n\nCentralized repository: Langsmith provides a central repository for storing and managing test cases, ensuring consistency and traceability.\n Collaboration and version control: Multiple users can collaborate on test cases, track changes, and maintain different versions.\nTest case prioritization: Langsmith can prioritize test cases based on risk, coverage, and business value.\n\nTest Execution**\n\n Test automation: Langsmith can generate automated test scripts from test cases, reducing manual effort and increasing efficiency.\n* Integration with test tools: Langsmith can integrate with various test tools, such as Selenium a…’

ChatModel 的输出(也就是这个 LLM 链的输出结果)是一条消息格式的内容。不过,在很多情况下,将输出结果作为普通文本字符串处理会更加方便。让我们加入一个简单的输出转换器,将聊天消息格式的输出转换为文本字符串。

1
2
3
from langchain_core.output_parsers import StrOutputParser

output_parser = StrOutputParser()

我们现在可以将此添加到之前的链中:

1
chain = prompt | llm | output_parser

我们现在可以调用它并提出同样的问题。答案现在将是一个字符串(而不是 ChatMessage)。

1
chain.invoke({"input": "how can langsmith help with testing?"})

当前使用 Gemini API 在 Colab 中这几次输出结果格式并没有什么区别,都是字符串格式。

Langsmith can assist with testing by:\n\n1. Automating Test Case Generation:\n\nGenerates test cases based on the specifications or requirements.\n Reduces manual effort and improves test coverage.\n\n2. Code Coverage Analysis:*\n\nAnalyzes the codebase and identifies areas not covered by tests.\n* Helps ensure comprehensive testing and reduces the risk of missed defects.\n\n3. Test Documentation Generation:\n\nAutomatically generates test plans, test cases, and reports.\n Streamlines documentation and improves communication among stakeholders.\n\n4. Assisted Testing:\n\nProvides AI-powered suggestions and guidance during manual testing.\n Identifies potential issues and helps testers focus on high-risk areas.\n\n5. Test Case Optimization:\n\nOptimizes test cases to reduce redundancy and increase efficiency.\n Reduces test execution time and improves overall testing productivity.\n\n6. Integration with Test Management Tools:*\n\n Integrates with…’

Retrieval Chain

为了准确地回答这个问题(”how can langsmith help with testing?”),我们需要向大语言模型(LLM)补充更多相关的信息。这可以通过信息检索来完成。当你手头的数据太多,不能直接全部传给大语言模型时,信息检索就显得尤为重要。你可以利用一个信息检索工具来筛选出与问题最相关的信息片段,并只将这些信息片段输入到模型中。

在这个流程中,我们会从一个检索工具中找到相关的文档,接着把这些文档内容嵌入到提示语中。检索工具的后端可以是多种形式:比如 SQL 表格、网络资源等。但在本例中,我们打算创建一个向量存储空间,并将其作为检索工具。

首先,我们需要准备好我们计划建立索引的数据。为了完成这一步骤,我们会利用 WebBaseLoader 这个工具。进行这个操作之前,需要先确保我们的环境中安装了 BeautifulSoup 这个库。

1
%pip install --upgrade --quiet beautifulsoup4

导入和使用 WebBaseLoader:

1
2
3
4
5
from langchain_community.document_loaders import WebBaseLoader

loader = WebBaseLoader("https://web.archive.org/web/20240324062817/https://docs.smith.langchain.com/user_guide")

docs = loader.load()

接下来,我们需要把数据索引进向量数据库。此过程需要借助一些关键组件:一个嵌入模型以及一个向量数据库。

至于如何使用嵌入模型,这里还是使用 Gemini API 的方式进行:

1
2
3
from langchain_google_genai import GoogleGenerativeAIEmbeddings

embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001", google_api_key=API_KEY)

现在,我们可以使用这个嵌入模型来导入文档至一个向量数据库。为了方便起见,我们此处选用一个简易的本地向量数据库:Facebook AI Similarity Search (FAISS)。

首先,我们需要安装必要的软件包:

1
%pip install --upgrade --quiet faiss-cpu

接下来,我们将创建我们的向量索引:

1
2
3
4
5
6
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)

现在,我们已经将数据索引入向量数据库中,下一步是构建一个检索链。此链会处理传入的问题,搜索相关的文档,随后将这些文档连同原先的提问一起提交给 LLM,让它来回答原始的问题。

首先,我们来搭建一个链,这个链能够处理用户提出的问题并根据检索到的文档生成回答。

1
2
3
4
5
6
7
8
9
10
11
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)

如果我们想的话,可以通过直接输入文档来亲自执行这一过程:

1
2
3
4
5
6
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")]
})

'Langsmith can help with testing by letting you visualize test results.'

然而,我们希望从我们刚刚设置的检索系统中得到文档。这样,我们就可以利用这个系统动态地筛选出针对特定问题最相关的文档,并用于生成回答。

1
2
3
4
from langchain.chains import create_retrieval_chain

retriever = vector.as_retriever()
retrieval_chain = create_retrieval_chain(retriever, document_chain)

现在,我们可以调用该链。这将返回一个字典类型的数据 - LLM 提供的响应将在 answer 键下找到。

1
2
response = retrieval_chain.invoke({"input": "how can langsmith help with testing?"})
print(response["answer"])

输出

LangSmith can help with testing by allowing developers to create datasets, which are collections of inputs and reference outputs, and use these to run tests on their LLM applications. These test cases can be uploaded in bulk, created on the fly, or exported from application traces. LangSmith also makes it easy to run custom evaluations (both LLM and heuristic based) to score test results.

Conversation Retrieval Chain

我们迄今为止构建的这个流程仅能回答单一问题。大语言模型的主流应用领域之一就是搭建聊天机器人。那么,我们应该如何改进这个流程,使它可以处理连续的对话问题呢?

我们可以继续使用 create_retrieval_chain 函数,但需要做出两方面的调整:

  1. 我们的检索方法现在应综合考虑整个对话的历史信息,而不是只针对最近的一次提问。
  2. 最终的 LLM 链,也需要将全部的历史信息纳入考虑。

为了改进检索过程,我们需创建一个新的链。这个链会处理最新的输入(input)与过往的对话内容(chat_history),并借助 LLM 生成搜索问题。

1
2
3
4
5
6
7
8
9
10
11
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)

我们可以尝试一个场景,其中用户提出了一个后续问题,来验证这个流程的效果。

1
2
3
4
5
6
7
from langchain_core.messages import HumanMessage, AIMessage

chat_history = [HumanMessage(content="Can LangSmith help test my LLM applications?"), AIMessage(content="Yes!")]
response = retriever_chain.invoke({
"chat_history": chat_history,
"input": "Tell me how"
})

你会发现,这样做检索出了有关于 LangSmith 测试的文档。这是因为 LLM 根据聊天的历史内容和追加的问题,生成了一条新的搜索命令。

既然我们已经拥有了这个新的检索工具,我们可以基于这些得到的文档,构建一条新的流程,以此继续进行对话。

1
2
3
4
5
6
7
8
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)

retrieval_chain = create_retrieval_chain(retriever_chain, document_chain)

现在,我们可以进行一次完整的测试:

1
2
3
4
5
6
7
chat_history = [HumanMessage(content="Can LangSmith help test my LLM applications?"), AIMessage(content="Yes!")]
response = retrieval_chain.invoke({
"chat_history": chat_history,
"input": "Tell me how"
})

print(response['answer'])

输出

LangSmith allows developers to create datasets, which are collections of inputs and reference outputs, and use these to run tests on their LLM applications. These test cases can be uploaded in bulk, created on the fly, or exported from application traces. LangSmith also makes it easy to run custom evaluations (both LLM and heuristic based) to score test results.

我们可以看到,这一系列动作让我们得到了一个条理清晰的答案 —— 这意味着,我们已经成功地把检索流程转化为了一个聊天机器人!

Agent

使用 Gemini API 来创建 Agent 过程要比官方基于 OpenAI 的示例复杂,具体代码可以参考 Gemini Agent Example,这里先暂时跳过。