Full Code of sugarforever/wtf-langchain for AI

main c91ccf6d903a cached
22 files
150.7 KB
50.2k tokens
1 requests
Download .txt
Repository: sugarforever/wtf-langchain
Branch: main
Commit: c91ccf6d903a
Files: 22
Total size: 150.7 KB

Directory structure:
gitextract_ok6e_leo/

├── 01_Hello_Langchain/
│   ├── Hello_Langchain.ipynb
│   └── README.md
├── 02_Models/
│   ├── Models.ipynb
│   └── README.md
├── 03_Data_Connections/
│   ├── 03_Data_Connections.ipynb
│   └── README.md
├── 04_Prompts/
│   ├── 04_Prompts.ipynb
│   └── README.md
├── 05_Output_Parsers/
│   ├── 05_Output_Parsers.ipynb
│   └── README.md
├── 06_Chains/
│   ├── 06_Chains.ipynb
│   └── README.md
├── 07_Memory/
│   ├── 07_Memory.ipynb
│   └── README.md
├── 08_Agents/
│   ├── 08_Agents.ipynb
│   └── README.md
├── 09_Callbacks/
│   ├── 09_Callbacks.ipynb
│   └── README.md
├── 10_Example/
│   ├── 10_Example.ipynb
│   └── README.md
├── LICENSE
└── README.md

================================================
FILE CONTENTS
================================================

================================================
FILE: 01_Hello_Langchain/Hello_Langchain.ipynb
================================================
{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/sugarforever/wtf-langchain/blob/main/01_Hello_Langchain/Hello_Langchain.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {
        "id": "F3PJ9w-JGMbL"
      },
      "outputs": [],
      "source": [
        "!pip install langchain==0.0.235 openai==0.28.1 -q -U"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "7t2nQzTEGXy1",
        "outputId": "b01545bc-b27e-4544-d527-adad64c0bfe8"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "content='Hello! How can I assist you today?' additional_kwargs={} example=False\n"
          ]
        }
      ],
      "source": [
        "from langchain.chat_models import ChatOpenAI\n",
        "from langchain.schema import HumanMessage\n",
        "\n",
        "import os\n",
        "os.environ['OPENAI_API_KEY'] = '您的有效OpenAI API Key'\n",
        "\n",
        "chat = ChatOpenAI(temperature=0)\n",
        "response = chat([ HumanMessage(content=\"Hello Langchain!\") ])\n",
        "print(response)"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "provenance": [],
      "include_colab_link": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}

================================================
FILE: 01_Hello_Langchain/README.md
================================================
---
title: 01. Hello Langchain
tags:
  - openai
  - llm
  - langchain
---

# WTF Langchain极简入门: 01. Hello Langchain

最近在学习Langchain框架,顺手写一个“WTF Langchain极简入门”,供小白们使用(编程大佬可以另找教程)。本教程默认以下前提:
- 使用Python版本的[Langchain](https://github.com/hwchase17/langchain)
- LLM使用OpenAI的模型
- Langchain目前还处于快速发展阶段,版本迭代频繁,为避免示例代码失效,本教程统一使用版本 **0.0.235**

根据Langchain的[代码约定](https://github.com/hwchase17/langchain/blob/v0.0.235/pyproject.toml#L14C1-L14C24),Python版本 ">=3.8.1,<4.0"。

推特:[@verysmallwoods](https://twitter.com/verysmallwoods)

所有代码和教程开源在github: [github.com/sugarforever/wtf-langchain](https://github.com/sugarforever/wtf-langchain)

-----

## Langchain 简介

大型语言模型(LLM)正在成为一种具有变革性的技术,使开发人员能够构建以前无法实现的应用程序。然而,仅仅依靠LLM还不足以创建一个真正强大的应用程序。它还需要其他计算资源或知识来源。

`Langchain` 旨在帮助开发这些类型应用程序,比如:
- 基于文档数据的问答
- 聊天机器人
- 代理

## OpenAI 简介

`OpenAI` 是LLM生态的模型层最大的玩家之一。大家目前熟知的 *GPT-3.5*,*GPT-4* 等模型都是OpenAI的产品。它的API允许开发人员通过简单的API调用来访问这些模型。

## Langchain与OpenAI

`Langchain` 作为一个开源框架,提供与OpenAI等语言模型的接口,简化了在应用程序中集成和利用语言模型能力的过程。

## 开发前的准备

在开始第一个Langchain应用程序之前,我们需要做一些必要的准备。

### Google Colab

本教程中,我们将使用 `Google Colab` 在云端运行 `Python` 代码。Google Colab(全称Google Colaboratory)是一个由Google提供的云端开发环境,用于数据分析、机器学习和深度学习任务。它基于Jupyter Notebook,提供了一个免费的、云端的Python编程环境,用户可以直接在浏览器中编写和执行Python代码。

网址:[https://colab.research.google.com/](https://colab.research.google.com/)

![Google Colab](./google_colab.png)

无法使用Google Colab的同学,可以使用Visual Studio Code配合Jupyter插件在本地运行代码。

### OpenAI API Key

在Langchain应用中使用OpenAI的模型,我们需要一个API Key。点击[https://platform.openai.com/account/api-keys](https://platform.openai.com/account/api-keys)创建你的API Key。

## 第一个 Langchain 应用

这个简单的程序只有 1 行安装指令和 7 行代码:

### 安装指令

```shell
pip install langchain==0.0.235 openai==0.28.2
```

### 代码

[Hello_Langchain.ipynb](./Hello_Langchain.ipynb)

```python
from langchain.chat_models import ChatOpenAI
from langchain.schema import HumanMessage

import os
os.environ['OPENAI_API_KEY'] = '您的有效OpenAI API Key'

chat = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo")
response = chat([ HumanMessage(content="Hello Langchain!") ])
print(response)
```

你应该能看到类似这样的输出:

```shell
content='Hello! How can I assist you today?' additional_kwargs={} example=False
```

我们拆解程序,学习该代码的结构:

1. 以下系统命令安装必要的Python包,langchain和openai。

  ```shell
  pip install langchain==0.0.235 openai
  ```

2. 以下代码将OpenAI的API Key设置在环境变量中。默认情况下,Langchain会从环境变量 `OPENAI_API_KEY` 中读取API Key。注意,在代码中直接嵌入API Key明文并不安全,切勿将API Key直接提交到代码仓库。我们建议利用.env文件和python-dotenv包来管理API Key。

  ```python
  import os
  os.environ['OPENAI_API_KEY'] = '您的有效OpenAI API Key'
  ```
    
3. 以下代码导入了 `ChatOpenAI` 类,该类封装了OpenAI的聊天模型。`ChatOpenAI` 类的初始化参数 `temperature` 用于控制模型的生成文本的多样性。`temperature` 越大,生成的文本越多样,但也越不可控。`temperature` 越小,生成的文本越单一,但也越可控。`temperature` 的取值范围为 0 到 1,默认值为 0.5。初始化参数 `model_name` 用于指定使用的模型,默认值为 `gpt-3.5-turbo`。

  ```python
  chat = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo")
  ```

4. 以下代码完成与OpenAI GPT模型的第一次对话,并打印出响应。

  ```python
  response = chat([ HumanMessage(content="Hello Langchain!") ])
  print(response)
  ```

## 运行代码

在 Google Colab 的页面,在每一个代码块,按 Ctrl/Cmd + Enter 即可运行代码,非常方便。

![](./hello_langchain.png)

## 总结
本节课程中,我们简要介绍了 `Langchain`,`OpenAI` 以及它们的关系,并完成了第一个 `Langchain` 应用 —— `Hello Langchain`。

### 相关文档资料链接:
1. [Python Langchain官方文档](https://python.langchain.com/docs/get_started/introduction.html) 

================================================
FILE: 02_Models/Models.ipynb
================================================
{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "view-in-github"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/sugarforever/wtf-langchain/blob/main/02_Models/Models.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "F3PJ9w-JGMbL"
      },
      "outputs": [],
      "source": [
        "# openai需要指定旧版本,否则会报 module 'openai' has no attribute 'error'\n",
        "!pip install langchain==0.0.235 openai==0.28.1"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "-p0HnkaSbBSe",
        "outputId": "3db21660-db1d-4a1b-8f64-dd92cf6d3dd2"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "\n",
            "AI (Artificial Intelligence) is the ability of a computer or machine to think and learn, and to act independently of humans. It includes the use of algorithms, data analysis, and robotics to carry out complex tasks that would normally require human intelligence. AI can be used to automate mundane tasks, improve decision-making processes, and create new products and services.\n"
          ]
        }
      ],
      "source": [
        "from langchain.llms import OpenAI\n",
        "\n",
        "import os\n",
        "os.environ['OPENAI_API_KEY'] = '您的有效OpenAI API Key'\n",
        "\n",
        "# text-davinci-003模型已经弃用,使用gpt-3.5-turbo进行测试\n",
        "llm = OpenAI(model_name=\"gpt-3.5-turbo\")\n",
        "response = llm.predict(\"What is AI?\")\n",
        "print(response)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "7t2nQzTEGXy1",
        "outputId": "addb81a7-e130-4bc7-dfa3-51e43197dbf6"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "content='AI, or Artificial Intelligence, refers to the simulation of human intelligence in machines that are programmed to think and learn like humans. It involves the development of computer systems capable of performing tasks that typically require human intelligence, such as visual perception, speech recognition, decision-making, problem-solving, and language translation. AI can be categorized into two types: Narrow AI, which is designed for specific tasks, and General AI, which possesses the ability to understand, learn, and apply knowledge across various domains.' additional_kwargs={} example=False\n"
          ]
        }
      ],
      "source": [
        "from langchain.chat_models import ChatOpenAI\n",
        "from langchain.schema import AIMessage, HumanMessage, SystemMessage\n",
        "\n",
        "import os\n",
        "os.environ['OPENAI_API_KEY'] = '您的有效OpenAI API Key'\n",
        "\n",
        "chat = ChatOpenAI(temperature=0, model_name='gpt-3.5-turbo')\n",
        "response = chat.predict_messages([\n",
        "  HumanMessage(content=\"What is AI?\")\n",
        "])\n",
        "print(response)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Z7En92dhwD0h",
        "outputId": "332773f9-0c22-40bf-9b7a-71d78cd18e68"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "langchain.schema.messages.AIMessage"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "response.__class__"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "ssIbQXyyq4Rk",
        "outputId": "e95da07f-e094-4b4b-cead-3f912f07a8ca"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "content=\"I'm sorry, but I don't know.\" additional_kwargs={} example=False\n"
          ]
        }
      ],
      "source": [
        "response = chat.predict_messages([\n",
        "  SystemMessage(content=\"You are a chatbot that knows nothing about AI. When you are asked about AI, you must say 'I don\\'t know'\"),\n",
        "  HumanMessage(content=\"What is deep learning?\")\n",
        "])\n",
        "print(response)"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "authorship_tag": "ABX9TyNDb1VJ0bhh4X+zrRM1TDh8",
      "include_colab_link": true,
      "provenance": []
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}


================================================
FILE: 02_Models/README.md
================================================
---
title: 02. 模型
tags:
  - openai
  - llm
  - langchain
---

# WTF Langchain极简入门: 02. 模型

最近在学习Langchain框架,顺手写一个“WTF Langchain极简入门”,供小白们使用(编程大佬可以另找教程)。本教程默认以下前提:
- 使用Python版本的[Langchain](https://github.com/hwchase17/langchain)
- LLM使用OpenAI的模型
- Langchain目前还处于快速发展阶段,版本迭代频繁,为避免示例代码失效,本教程统一使用版本 **0.0.235**

根据Langchain的[代码约定](https://github.com/hwchase17/langchain/blob/v0.0.235/pyproject.toml#L14C1-L14C24),Python版本 ">=3.8.1,<4.0"。

推特:[@verysmallwoods](https://twitter.com/verysmallwoods)

所有代码和教程开源在github: [github.com/sugarforever/wtf-langchain](https://github.com/sugarforever/wtf-langchain)

-----

## 模型简介

Langchain所封装的模型分为两类:
- 大语言模型 (LLM)
- 聊天模型 (Chat Models)

在后续的内容中,为简化描述,我们将使用 `LLM` 来指代大语言模型。

Langchain的支持众多模型供应商,包括OpenAI、ChatGLM、HuggingFace等。本教程中,我们将以OpenAI为例,后续内容中提到的模型默认为OpenAI提供的模型。

Langchain的封装,比如,对OpenAI模型的封装,实际上是指的是对OpenAI API的封装。

### LLM

`LLM` 是一种基于统计的机器学习模型,用于对文本数据进行建模和生成。LLM学习和捕捉文本数据中的语言模式、语法规则和语义关系,以生成连贯并合乎语言规则的文本。

在Langchain的环境中,LLM特指文本补全模型(text completion model)。

注,文本补全模型是一种基于语言模型的机器学习模型,根据上下文的语境和语言规律,自动推断出最有可能的下一个文本补全。

| 输入 | 输出 |
| -------- | ------- |
| 一条文本内容 | 一条文本内容 |

### 聊天模型 (Chat Models)

聊天模型是语言模型的一种变体。聊天模型使用语言模型,并提供基于"聊天消息"的接口。

| 输入 | 输出 |
| -------- | ------- |
| 一组聊天消息 | 一条聊天消息 |

`聊天消息` 除了消息内容文本,还会包含一些其他参数数据。这在后续的内容中会看到。

## Langchain与OpenAI模型

参考OpenAI [Model endpoint compatibility](https://platform.openai.com/docs/models/model-endpoint-compatibility) 文档,gpt模型都归为了聊天模型,而davinci, curie, babbage, ada模型都归为了文本补全模型。

| ENDPOINT | MODEL NAME |
| -------- | ------- |
| /v1/chat/completions | gpt-4, gpt-4-0613, gpt-4-32k, gpt-4-32k-0613, gpt-3.5-turbo, gpt-3.5-turbo-0613, gpt-3.5-turbo-16k, gpt-3.5-turbo-16k-0613 |
| /v1/completions | (Legacy)	text-davinci-003, text-davinci-002, text-davinci-001, text-curie-001, text-babbage-001, text-ada-001, davinci, curie, babbage, ada |

Langchain提供接口集成不同的模型。为了便于切换模型,Langchain将不同模型抽象为相同的接口 `BaseLanguageModel`,并提供 `predict` 和 `predict_messages` 函数来调用模型。

当使用LLM时推荐使用predict函数,当使用聊天模型时推荐使用predict_messages函数。

## 示例代码

接下来我们来看看如何在Langchain中使用LLM和聊天模型。

[Models.ipynb](./Models.ipynb)
### 与LLM的交互

与LLM的交互,我们需要使用 `langchain.llms` 模块中的 `OpenAI` 类。

```python
from langchain.llms import OpenAI

import os
os.environ['OPENAI_API_KEY'] = '您的有效OpenAI API Key'

llm = OpenAI(model_name="text-davinci-003")
response = llm.predict("What is AI?")
print(response)
```

你应该能看到类似如下输出:

```shell
AI (Artificial Intelligence) is a branch of computer science that deals with creating intelligent machines that can think, reason, learn, and problem solve. AI systems are designed to mimic human behavior and can be used to automate tasks or provide insights into data. AI can be used in a variety of fields, such as healthcare, finance, robotics, and more.
```

### 与聊天模型的交互

与聊天模型的交互,我们需要使用 `langchain.chat_models` 模块中的 `ChatOpenAI` 类。

```python
from langchain.chat_models import ChatOpenAI
from langchain.schema import AIMessage, HumanMessage, SystemMessage

import os
os.environ['OPENAI_API_KEY'] = '您的有效OpenAI API Key'

chat = ChatOpenAI(temperature=0)
response = chat.predict_messages([ 
  HumanMessage(content="What is AI?")
])
print(response)
```

你应该能看到类似如下输出:

```shell
content='AI, or Artificial Intelligence, refers to the simulation of human intelligence processes by machines, especially computer systems. These processes include learning, reasoning, problem-solving, perception, and language understanding. AI technology has the capability to drastically change and improve the way we work, live, and interact.' additional_kwargs={} example=False
```

通过以下代码我们查看一下 `response` 变量的类型:

```python
response.__class
```

可以看到,它是一个 `AIMessage` 类型的对象。

```shell
langchain.schema.messages.AIMessage
```

接下来我们使用 `SystemMessage` 指令来指定模型的行为。如下代码指定模型对AI一无所知,在回答AI相关问题时,回答“I don't know”。

```python
response = chat.predict_messages([
  SystemMessage(content="You are a chatbot that knows nothing about AI. When you are asked about AI, you must say 'I don\'t know'"),
  HumanMessage(content="What is deep learning?")
])
print(response)
```

你应该能看到类似如下输出:

```shell
content="I don't know." additional_kwargs={} example=False
```

#### 3个消息类

Langchain框架提供了三个消息类,分别是 `AIMessage`、`HumanMessage` 和 `SystemMessage`。它们对应了OpenAI聊天模型API支持的不同角色 `assistant`、`user` 和 `system`。请参考 [OpenAI API文档 - Chat - Role](https://platform.openai.com/docs/api-reference/chat/create#chat/create-role)。

| Langchain类 | OpenAI角色 | 作用 |
| -------- | ------- | ------- |
| AIMessage | assistant | 模型回答的消息 |
| HumanMessage | user | 用户向模型的请求或提问 |
| SystemMessage | system | 系统指令,用于指定模型的行为 |

## 总结

本节课程中,我们学习了模型的基本概念,LLM与聊天模型的差异,并基于 `Langchain` 实现了分别与OpenAI LLM和聊天模型的交互。

要注意,虽然是聊天,但是当前我们所实现的交互并没有记忆能力,也就是说,模型并不会记住之前的对话内容。在后续的内容中,我们将学习如何实现记忆能力。

### 相关文档资料链接:
1. [Python Langchain官方文档](https://python.langchain.com/docs/get_started/introduction.html) 
2. [Models - Langchain](https://python.langchain.com/docs/modules/model_io/models/)

================================================
FILE: 03_Data_Connections/03_Data_Connections.ipynb
================================================
{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": [],
      "authorship_tag": "ABX9TyOgQoOin53yoGqil3iR6M6W",
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/sugarforever/wtf-langchain/blob/main/03_Data_Connections/03_Data_Connections.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "MjW9VjNto59d"
      },
      "outputs": [],
      "source": [
        "!pip install -q langchain==0.0.235 openai"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "!wget https://raw.githubusercontent.com/WTFAcademy/WTF-Langchain/main/01_Hello_Langchain/README.md"
      ],
      "metadata": {
        "id": "LS_efmfC5Hp6"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 加载文档"
      ],
      "metadata": {
        "id": "C-W2t1v65-Gt"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.document_loaders import TextLoader\n",
        "\n",
        "loader = TextLoader(\"./README.md\")\n",
        "docs = loader.load()"
      ],
      "metadata": {
        "id": "e1_VoFqS5GJ4"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "docs"
      ],
      "metadata": {
        "id": "omltifXH6jc7"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 拆分文档"
      ],
      "metadata": {
        "id": "kRPF6Mfn6Ake"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 按字符拆分"
      ],
      "metadata": {
        "id": "FakX37SB6DT4"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.text_splitter import CharacterTextSplitter\n",
        "text_splitter = CharacterTextSplitter(\n",
        "    separator = \"\\n\\n\",\n",
        "    chunk_size = 1000,\n",
        "    chunk_overlap  = 200,\n",
        "    length_function = len,\n",
        ")\n",
        "\n",
        "split_docs = text_splitter.split_documents(docs)\n",
        "print(len(docs[0].page_content))\n",
        "for split_doc in split_docs:\n",
        "  print(len(split_doc.page_content))"
      ],
      "metadata": {
        "id": "0gm-A-_r5Wfb"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 拆分代码"
      ],
      "metadata": {
        "id": "8avQDR6u6HCP"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.text_splitter import RecursiveCharacterTextSplitter, Language\n",
        "\n",
        "PYTHON_CODE = \"\"\"\n",
        "def hello_langchain():\n",
        "    print(\"Hello, Langchain!\")\n",
        "\n",
        "# Call the function\n",
        "hello_langchain()\n",
        "\"\"\"\n",
        "python_splitter = RecursiveCharacterTextSplitter.from_language(\n",
        "    language=Language.PYTHON, chunk_size=50, chunk_overlap=0\n",
        ")\n",
        "python_docs = python_splitter.create_documents([PYTHON_CODE])\n",
        "python_docs"
      ],
      "metadata": {
        "id": "OlNC7pR15Z0r"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Markdown文档拆分"
      ],
      "metadata": {
        "id": "O_wWWlWS6JkO"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.text_splitter import MarkdownHeaderTextSplitter\n",
        "\n",
        "markdown_document = \"# Chapter 1\\n\\n    ## Section 1\\n\\nHi this is the 1st section\\n\\nWelcome\\n\\n ### Module 1 \\n\\n Hi this is the first module \\n\\n ## Section 2\\n\\n Hi this is the 2nd section\"\n",
        "\n",
        "headers_to_split_on = [\n",
        "    (\"#\", \"Header 1\"),\n",
        "    (\"##\", \"Header 2\"),\n",
        "    (\"###\", \"Header 3\"),\n",
        "]\n",
        "\n",
        "splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)\n",
        "splits = splitter.split_text(markdown_document)\n",
        "\n",
        "splits"
      ],
      "metadata": {
        "id": "Gg6twioR5cX8"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 按字符递归拆分"
      ],
      "metadata": {
        "id": "Spo_Nn036Oko"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
        "text_splitter = RecursiveCharacterTextSplitter(\n",
        "    chunk_size = 100,\n",
        "    chunk_overlap  = 20,\n",
        "    length_function = len,\n",
        ")\n",
        "texts = text_splitter.split_documents(docs)\n",
        "print(len(docs[0].page_content))\n",
        "for split_doc in texts:\n",
        "  print(len(split_doc.page_content))"
      ],
      "metadata": {
        "id": "RLxIWV3G5nSh"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 按token拆分"
      ],
      "metadata": {
        "id": "iH_AHoif6SVQ"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "!pip install -q tiktoken"
      ],
      "metadata": {
        "id": "L0zcXo2y8urg"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.text_splitter import CharacterTextSplitter\n",
        "text_splitter = CharacterTextSplitter.from_tiktoken_encoder(\n",
        "    chunk_size=100, chunk_overlap=0\n",
        ")\n",
        "split_docs = text_splitter.split_documents(docs)\n",
        "\n",
        "split_docs"
      ],
      "metadata": {
        "id": "WGg-ZOaq5pzl"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 向量化文档分块"
      ],
      "metadata": {
        "id": "d8dfw22O6Vb2"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.embeddings import OpenAIEmbeddings\n",
        "embeddings_model = OpenAIEmbeddings(openai_api_key=\"\")\n",
        "embeddings = embeddings_model.embed_documents(\n",
        "    [\n",
        "        \"你好!\",\n",
        "        \"Langchain!\",\n",
        "        \"你真棒!\"\n",
        "    ]\n",
        ")\n",
        "embeddings"
      ],
      "metadata": {
        "id": "AghMYu8r5zBW"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 向量数据存储"
      ],
      "metadata": {
        "id": "QYq8gm4g6ZBl"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 存储"
      ],
      "metadata": {
        "id": "Jff1dIkk6cwh"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "!pip install -q chromadb"
      ],
      "metadata": {
        "id": "3KT-ziYSMMn9"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.document_loaders import TextLoader\n",
        "from langchain.embeddings.openai import OpenAIEmbeddings\n",
        "from langchain.text_splitter import CharacterTextSplitter\n",
        "from langchain.vectorstores import Chroma\n",
        "\n",
        "text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n",
        "documents = text_splitter.split_documents(docs)\n",
        "db = Chroma.from_documents(documents, OpenAIEmbeddings(openai_api_key=\"\"))"
      ],
      "metadata": {
        "id": "vtDRMAx752w_"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 检索"
      ],
      "metadata": {
        "id": "3SmtLL016f5l"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "query = \"什么是WTF Langchain?\"\n",
        "docs = db.similarity_search(query)\n",
        "docs"
      ],
      "metadata": {
        "id": "XqqP4P4554j5"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "docs = db.similarity_search_with_score(query)\n",
        "docs"
      ],
      "metadata": {
        "id": "bAa13Y7DM-rO"
      },
      "execution_count": null,
      "outputs": []
    }
  ]
}

================================================
FILE: 03_Data_Connections/README.md
================================================
---
title: 03. 数据连接
tags:
  - openai
  - llm
  - langchain
---

# WTF Langchain极简入门: 03. 数据连接

最近在学习Langchain框架,顺手写一个“WTF Langchain极简入门”,供小白们使用(编程大佬可以另找教程)。本教程默认以下前提:
- 使用Python版本的[Langchain](https://github.com/hwchase17/langchain)
- LLM使用OpenAI的模型
- Langchain目前还处于快速发展阶段,版本迭代频繁,为避免示例代码失效,本教程统一使用版本 **0.0.235**

根据Langchain的[代码约定](https://github.com/hwchase17/langchain/blob/v0.0.235/pyproject.toml#L14C1-L14C24),Python版本 ">=3.8.1,<4.0"。

推特:[@verysmallwoods](https://twitter.com/verysmallwoods)

所有代码和教程开源在github: [github.com/sugarforever/wtf-langchain](https://github.com/sugarforever/wtf-langchain)

-----

## 什么是数据连接?

LLM应用往往需要用户特定的数据,而这些数据并不属于模型的训练集。`LangChain` 的数据连接概念,通过提供以下组件,实现用户数据的加载、转换、存储和查询:

- 文档加载器:从不同的数据源加载文档
- 文档转换器:拆分文档,将文档转换为问答格式,去除冗余文档,等等
- 文本嵌入模型:将非结构化文本转换为浮点数数组表现形式,也称为向量
- 向量存储:存储和搜索嵌入数据(向量)
- 检索器:提供数据查询的通用接口

我们通过下一段落的实践,来介绍这些组件的使用。
## 数据连接实践

在LLM应用连接用户数据时,通常我们会以如下步骤完成:
1. 加载文档
2. 拆分文档
3. 向量化文档分块
4. 向量数据存储

这样,我们就可以通过向量数据的检索器,来查询用户数据。接下来我们看看每一步的代码实现示例。最后,我们将通过一个完整的示例来演示如何使用数据连接。

### 加载文档

`Langchain` 提供了多种文档加载器,用于从不同的数据源加载不同类型的文档。比如,我们可以从本地文件系统加载文档,也可以通过网络加载远程数据。想了解 `Langchain` 所支持的所有文档加载器,请参考[Document Loaders](https://python.langchain.com/docs/integrations/document_loaders/)。

在本节课程中,我们将使用最基本的 `TextLoader` 来加载本地文件系统中的文档。代码如下:

```python
from langchain.document_loaders import TextLoader

loader = TextLoader("./README.md")
docs = loader.load()
```

在上述代码中,我们使用 `TextLoader` 加载了本地文件系统中的 `./README.md` 文件。`TextLoader` 的 `load` 方法返回一个 `Document` 对象数组(`Document` 是 `Langchain` 提供的文档类,包含原始内容和元数据)。我们可以通过 `Document` 对象的 `content` 属性来访问文档的原始内容。

完整代码请参考[本节课程的示例代码](./03_Data_Connections.ipynb)。

### 拆分文档

拆分文档是文档转换中最常见的操作。拆分文档的目的是将文档拆分为更小的文档块,以便更好地利用模型。当我们要基于长篇文本构建QA应用时,必须将文本分割成块,这样才能在数据查询中,基于相似性找到与问题最接近的文本块。这也是常见的AI问答机器人的工作原理。

`Langchain` 提供了多种文档拆分器,用于将文档拆分为更小的文档块。我们逐个看看这些拆分器的使用方法。

#### 按字符拆分

`CharacterTextSplitter` 是最简单的文档拆分器,它将文档拆分为固定长度的文本块。代码如下:

```python
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(        
    separator = "\n\n",
    chunk_size = 1000,
    chunk_overlap  = 200,
    length_function = len,
)

split_docs = text_splitter.split_documents(docs)
```

#### 拆分代码

`RecursiveCharacterTextSplitter` 的 `from_language` 函数,可以根据编程语言的特性,将代码拆分为合适的文本块。代码如下:

```pyhon
from langchain.text_splitter import Language,RecursiveCharacterTextSplitter
PYTHON_CODE = """
def hello_langchain():
    print("Hello, Langchain!")

# Call the function
hello_langchain()
"""
python_splitter = RecursiveCharacterTextSplitter.from_language(
    language=Language.PYTHON, chunk_size=50, chunk_overlap=0
)
python_docs = python_splitter.create_documents([PYTHON_CODE])
python_docs
```

#### Markdown文档拆分

`MarkdownHeaderTextSplitter` 可以将Markdown文档按照段落结构,基于Markdown语法进行文档分块。代码如下:

```python
from langchain.text_splitter import MarkdownHeaderTextSplitter

markdown_document = "# Chapter 1\n\n    ## Section 1\n\nHi this is the 1st section\n\nWelcome\n\n ### Module 1 \n\n Hi this is the first module \n\n ## Section 2\n\n Hi this is the 2nd section"

headers_to_split_on = [
    ("#", "Header 1"),
    ("##", "Header 2"),
    ("###", "Header 3"),
]

splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
splits = splitter.split_text(markdown_document)
```

#### 按字符递归拆分

这也是对于普通文本的推荐拆分方式。它通过一组字符作为参数,按顺序尝试使用这些字符进行拆分,直到块的大小足够小为止。默认的字符列表是["\n\n", "\n", " ", ""]。它尽可能地保持段落,句子,单词的完整,因此尽可能地保证语义完整。

```python
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
    # Set a really small chunk size, just to show.
    chunk_size = 100,
    chunk_overlap  = 20,
    length_function = len,
)
texts = text_splitter.split_documents(docs)
```
#### 按token拆分

语言模型,例如OpenAI,有token限制。在API调用中,不应超过token限制。看来,在将文本分块时,根据token数来拆分也是不错的主意。有许多令牌化工具,因此当计算文本的token数时,应该使用与语言模型相同的令牌化工具。

注,OpenAI所使用的是[tiktoken](https://github.com/openai/tiktoken)。

```python
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=100, chunk_overlap=0
)
split_docs = text_splitter.split_documents(docs)
```
### 向量化文档分块

`Langchain` 的 `Embeddings` 类实现与文本嵌入模型进行交互的标准接口。当前市场上有许多嵌入模型提供者(如OpenAI、Cohere、Hugging Face等)。

嵌入模型创建文本片段的向量表示。这意味着我们可以在向量空间中处理文本,并进行语义搜索等操作,从而查找在向量空间中最相似的文本片段。

注,文本之间的相似度由其向量表示的欧几里得距离来衡量。欧几里得距离是最常见的距离度量方式,也称为L2范数。对于两个n维向量a和b,欧几里得距离可以通过以下公式计算:

```math
d(a, b) = √((a₁ - b₁)² + (a₂ - b₂)² + ... + (aₙ - bₙ)²)
```

当使用OpenAI的文本嵌入模型时,我们使用如下代码来创建文本片段的向量表示:

```python
from langchain.embeddings import OpenAIEmbeddings
embeddings_model = OpenAIEmbeddings(openai_api_key="你的有效openai api key")
embeddings = embeddings_model.embed_documents(
    [
        "你好!",
        "Langchain!",
        "你真棒!"
    ]
)
embeddings
```

你应该能看到如下输出:

```shell
[[0.001767348474591444,
  -0.016549955833298362,
  0.009669921232251705,
  -0.024465152668289573,
  -0.04928377577655549,
  ...],
  [...
  -0.026084684286027195,
  -0.0023797790465254626,
  0.006104789779720747,
  ...]]
```

注,当我们在环境变量中设置了 `OPENAI_API_KEY`,在上述代码 `OpenAIEmbeddings(openai_api_key="你的有效openai api key")` 中,参数 `openai_api_key` 可以省略。

```shell
export OPENAI_API_KEY="..."
```

### 向量数据存储

向量数据存储,或成为向量数据库,负责存储文本嵌入的向量表现,并提供向量检索的能力。`Langchain` 提供了多种开源或商业向量数据存储,包括:Chroma, FAISS, Pinecone等。

本讲以Chroma为例。

#### 存储

`Langchain` 提供了 `Chroma` 包装类,封装了chromadb的操作。

在进行以下代码执行前,需要安装 `Chroma` 的包:

```shell
pip install -q chromadb
```

```python
from langchain.document_loaders import TextLoader
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma

text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
documents = text_splitter.split_documents(docs)
db = Chroma.from_documents(documents, OpenAIEmbeddings())
```

#### 检索

向量数据库提供的重要接口就是相似性查询。如上述内容提到,文本相似性的衡量,由文本的向量表示的欧几里得距离来衡量。向量数据库提供了该接口,用于查询与给定文本最相似的文本。

```python
query = "什么是WTF Langchain?"
docs = db.similarity_search(query)
print(docs[0].page_content)
```

## 总结
本节课程中,我们简要介绍了 `Langchain` 的数据连接概念,并完成了以下任务:
1. 了解常见的文档加载器,
2. 了解常见的文档拆分方法
3. 掌握如何利用OpenAI实现文本的向量化和向量数据的存储与查询。

### 相关文档资料链接:
1. [Python Langchain官方文档](https://python.langchain.com/docs/get_started/introduction.html) 
2. [本节课程的示例代码](./03_Data_Connections.ipynb)

================================================
FILE: 04_Prompts/04_Prompts.ipynb
================================================
{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": [],
      "authorship_tag": "ABX9TyM7vOFz/ctj6hEBoPcJqCn6",
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/sugarforever/wtf-langchain/blob/main/04_Prompts/04_Prompts.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# 04 提示词\n",
        "\n",
        "## 什么是提示词?\n",
        "\n",
        "提示词(`Prompt`)是指向模型提供的输入。这个输入通常由多个元素构成。`LangChain` 提供了一系列的类和函数,简化构建和处理提示词的过程。\n",
        "- 提示词模板(Prompt Template):对提示词参数化,提高代码的重用性。\n",
        "- 示例选择器(Example Selector):动态选择要包含在提示词中的示例\n",
        "\n",
        "## 提示词模板\n",
        "\n",
        "提示词模板提供了可重用提示词的机制。用户通过传递一组参数给模板,实例化图一个提示词。一个提示模板可以包含:\n",
        "1. 对语言模型的指令\n",
        "2. 一组少样本示例,以帮助语言模型生成更好的回复\n",
        "3. 向语言模型提出的问题"
      ],
      "metadata": {
        "id": "IkIusM-GD9MR"
      }
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Ceq3MMqkDutF",
        "outputId": "b71546ca-c764-40db-b8e9-c75da5aa357f"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.3/1.3 MB\u001b[0m \u001b[31m7.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m90.0/90.0 kB\u001b[0m \u001b[31m9.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m3.1/3.1 MB\u001b[0m \u001b[31m18.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m49.4/49.4 kB\u001b[0m \u001b[31m4.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25h"
          ]
        }
      ],
      "source": [
        "!pip install -q langchain==0.0.235"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain import PromptTemplate\n",
        "template = \"\"\"\n",
        "你精通多种语言,是专业的翻译官。你负责{src_lang}到{dst_lang}的翻译工作。\n",
        "\"\"\"\n",
        "\n",
        "prompt = PromptTemplate.from_template(template)\n",
        "prompt.format(src_lang=\"英文\", dst_lang=\"中文\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 36
        },
        "id": "JLEcB491EGTI",
        "outputId": "e01c7610-0776-4801-be39-b8d6816ee207"
      },
      "execution_count": 2,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "'\\n你精通多种语言,是专业的翻译官。你负责英文到中文的翻译工作。\\n'"
            ],
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            }
          },
          "metadata": {},
          "execution_count": 2
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 创建模板\n",
        "\n",
        "`PromptTemplate` 类是 `LangChain` 提供的模版基础类,它接收两个参数:\n",
        "1. `input_variables` - 输入变量\n",
        "2. `template` - 模版"
      ],
      "metadata": {
        "id": "_DRl_mS9EUl8"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "multiple_input_prompt = PromptTemplate(\n",
        "    input_variables=[\"color\", \"animal\"],\n",
        "    template=\"A {color} {animal} .\"\n",
        ")\n",
        "multiple_input_prompt.format(color=\"black\", animal=\"bear\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 36
        },
        "id": "T349YtlREVlc",
        "outputId": "682a2e75-18f9-4aba-b2d6-18e2e5d7b07a"
      },
      "execution_count": 3,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "'A black bear .'"
            ],
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            }
          },
          "metadata": {},
          "execution_count": 3
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "#### 聊天提示词模板\n",
        "\n",
        "聊天模型,比如 `OpenAI` 的GPT模型,接受一系列聊天消息作为输入,每条消息都与一个角色相关联。这个消息列表通常以一定格式串联,构成模型的输入,也就是提示词。"
      ],
      "metadata": {
        "id": "SLaylT92Eeu_"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.prompts import (\n",
        "    ChatPromptTemplate,\n",
        "    PromptTemplate,\n",
        "    SystemMessagePromptTemplate,\n",
        "    AIMessagePromptTemplate,\n",
        "    HumanMessagePromptTemplate,\n",
        ")\n",
        "from langchain.schema import (\n",
        "    AIMessage,\n",
        "    HumanMessage,\n",
        "    SystemMessage\n",
        ")\n",
        "\n",
        "system_template=\"You are a professional translator that translates {src_lang} to {dst_lang}.\"\n",
        "system_message_prompt = SystemMessagePromptTemplate.from_template(system_template)\n",
        "\n",
        "human_template=\"{user_input}\"\n",
        "human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)\n",
        "\n",
        "chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])\n",
        "chat_prompt.format_prompt(\n",
        "    src_lang=\"English\",\n",
        "    dst_lang=\"Chinese\",\n",
        "    user_input=\"Did you eat in this morning?\"\n",
        ").to_messages()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "LH_VDH6iEjY8",
        "outputId": "3b1cad37-3375-4b50-9ebe-505b7085d471"
      },
      "execution_count": 5,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[SystemMessage(content='You are a professional translator that translates English to Chinese.', additional_kwargs={}),\n",
              " HumanMessage(content='Did you eat in this morning?', additional_kwargs={}, example=False)]"
            ]
          },
          "metadata": {},
          "execution_count": 5
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "#### 样本选择器"
      ],
      "metadata": {
        "id": "YL1RwGrXEyKg"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.prompts import PromptTemplate\n",
        "from langchain.prompts import FewShotPromptTemplate\n",
        "from langchain.prompts.example_selector import LengthBasedExampleSelector\n",
        "\n",
        "examples = [\n",
        "    {\"input\": \"happy\", \"output\": \"sad\"},\n",
        "    {\"input\": \"tall\", \"output\": \"short\"},\n",
        "    {\"input\": \"energetic\", \"output\": \"lethargic\"},\n",
        "    {\"input\": \"sunny\", \"output\": \"gloomy\"},\n",
        "    {\"input\": \"windy\", \"output\": \"calm\"},\n",
        "]\n",
        "example_prompt = PromptTemplate(\n",
        "    input_variables=[\"input\", \"output\"],\n",
        "    template=\"Input: {input}\\nOutput: {output}\",\n",
        ")\n",
        "example_selector = LengthBasedExampleSelector(\n",
        "    # 可选的样本数据\n",
        "    examples=examples,\n",
        "    # 提示词模版\n",
        "    example_prompt=example_prompt,\n",
        "    # 格式化的样本数据的最大长度,通过get_text_length函数来衡量\n",
        "    max_length=4,\n",
        "    # get_text_length: ...\n",
        ")\n",
        "dynamic_prompt = FewShotPromptTemplate(\n",
        "    example_selector=example_selector,\n",
        "    example_prompt=example_prompt,\n",
        "    prefix=\"Give the antonym of every input\",\n",
        "    suffix=\"Input: {adjective}\\nOutput:\",\n",
        "    input_variables=[\"adjective\"],\n",
        ")\n",
        "\n",
        "# 输入量极小,因此所有样本数据都会被选中\n",
        "print(dynamic_prompt.format(adjective=\"big\"))"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Aei66914EywY",
        "outputId": "51606e73-35a0-4d95-9714-31cba534862a"
      },
      "execution_count": 11,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Give the antonym of every input\n",
            "\n",
            "Input: big\n",
            "Output:\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "example_selector.get_text_length(\"\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "vehXwrdxG08-",
        "outputId": "b8665780-fbf7-4329-dd3d-5e0602837bf0"
      },
      "execution_count": 12,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "1"
            ]
          },
          "metadata": {},
          "execution_count": 12
        }
      ]
    }
  ]
}

================================================
FILE: 04_Prompts/README.md
================================================
---
title: 04. 提示词
tags:
  - openai
  - llm
  - langchain
---

# WTF Langchain极简入门: 04. 提示词

最近在学习Langchain框架,顺手写一个“WTF Langchain极简入门”,供小白们使用(编程大佬可以另找教程)。本教程默认以下前提:
- 使用Python版本的[Langchain](https://github.com/hwchase17/langchain)
- LLM使用OpenAI的模型
- Langchain目前还处于快速发展阶段,版本迭代频繁,为避免示例代码失效,本教程统一使用版本 **0.0.235**

根据Langchain的[代码约定](https://github.com/hwchase17/langchain/blob/v0.0.235/pyproject.toml#L14C1-L14C24),Python版本 ">=3.8.1,<4.0"。

推特:[@verysmallwoods](https://twitter.com/verysmallwoods)

所有代码和教程开源在github: [github.com/sugarforever/wtf-langchain](https://github.com/sugarforever/wtf-langchain)

-----

## 什么是提示词?

提示词(`Prompt`)是指向模型提供的输入。这个输入通常由多个元素构成。`LangChain` 提供了一系列的类和函数,简化构建和处理提示词的过程。
- 提示词模板(Prompt Template):对提示词参数化,提高代码的重用性。
- 示例选择器(Example Selector):动态选择要包含在提示词中的示例

## 提示词模板

提示词模板提供了可重用提示词的机制。用户通过传递一组参数给模板,实例化图一个提示词。一个提示模板可以包含:
1. 对语言模型的指令
2. 一组少样本示例,以帮助语言模型生成更好的回复
3. 向语言模型提出的问题

一个简单的例子:

```python
from langchain import PromptTemplate
template = """
你精通多种语言,是专业的翻译官。你负责{src_lang}到{dst_lang}的翻译工作。
"""

prompt = PromptTemplate.from_template(template)
prompt.format(src_lang="英文", dst_lang="中文")
```

### 创建模板

`PromptTemplate` 类是 `LangChain` 提供的模版基础类,它接收两个参数:
1. `input_variables` - 输入变量
2. `template` - 模版

模版中通过 `{}` 符号来引用输入变量,比如 `PromptTemplate(input_variables=["name"], template="My name is {name}.")`。

模版的实例化通过模板类实例的 `format`函数实现。例子如下:

```python
multiple_input_prompt = PromptTemplate(
    input_variables=["color", "animal"], 
    template="A {color} {animal} ."
)
multiple_input_prompt.format(color="black", animal="bear")
```

#### 聊天提示词模板

聊天模型,比如 `OpenAI` 的GPT模型,接受一系列聊天消息作为输入,每条消息都与一个角色相关联。这个消息列表通常以一定格式串联,构成模型的输入,也就是提示词。

例如,在OpenAI [Chat Completion API](https://platform.openai.com/docs/api-reference/chat/create)中,聊天消息可以与assistant、human或system角色相关联。

为此,LangChain提供了一系列模板,以便更轻松地构建和处理提示词。建议在与聊天模型交互时优先选择使用这些与聊天相关的模板,而不是基础的PromptTemplate,以充分利用框架的优势,提高开发效率。`SystemMessagePromptTemplate`, `AIMessagePromptTemplate`, `HumanMessagePromptTemplate` 是分别用于创建不同角色提示词的模板。

我们来看一个综合示例:

```python
from langchain.prompts import (
    ChatPromptTemplate,
    PromptTemplate,
    SystemMessagePromptTemplate,
    AIMessagePromptTemplate,
    HumanMessagePromptTemplate,
)
from langchain.schema import (
    AIMessage,
    HumanMessage,
    SystemMessage
)

system_template="You are a professional translator that translates {src_lang} to {dst_lang}."
system_message_prompt = SystemMessagePromptTemplate.from_template(system_template)

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

chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])
chat_prompt.format_prompt(
    src_lang="English",
    dst_lang="Chinese", 
    user_input="Did you eat in this morning?"
).to_messages()
```

你应该能看到如下输出:
```shell
[SystemMessage(content='You are a professional translator that translates English to Chinese.', additional_kwargs={}),
 HumanMessage(content='Did you eat in this morning?', additional_kwargs={}, example=False)]
```

#### 样本选择器

在LLM应用开发中,可能需要从大量样本数据中,选择部分数据包含在提示词中。样本选择器(Example Selector)正是满足该需求的组件,它也通常与少样本提示词配合使用。`LangChain` 提供了样本选择器的基础接口类 `BaseExampleSelector`,每个选择器类必须实现的函数为 `select_examples`。`LangChain` 实现了若干基于不用应用场景或算法的选择器:
- LengthBasedExampleSelector 
- MaxMarginalRelevanceExampleSelector
- NGramOverlapExampleSelector
- SemanticSimilarityExampleSelector

本讲以基于长度的样本选择器(输入越长,选择的样本越少;输入越短,选择的样本越多)`LengthBasedExampleSelector` 为例,演示用法。

```python
from langchain.prompts import PromptTemplate
from langchain.prompts import FewShotPromptTemplate
from langchain.prompts.example_selector import LengthBasedExampleSelector


# These are a lot of examples of a pretend task of creating antonyms.
examples = [
    {"input": "happy", "output": "sad"},
    {"input": "tall", "output": "short"},
    {"input": "energetic", "output": "lethargic"},
    {"input": "sunny", "output": "gloomy"},
    {"input": "windy", "output": "calm"},
]
example_prompt = PromptTemplate(
    input_variables=["input", "output"],
    template="Input: {input}\nOutput: {output}",
)
example_selector = LengthBasedExampleSelector(
    # 可选的样本数据
    examples=examples, 
    # 提示词模版
    example_prompt=example_prompt, 
    # 格式化的样本数据的最大长度,通过get_text_length函数来衡量
    max_length=25
)
dynamic_prompt = FewShotPromptTemplate(
    example_selector=example_selector,
    example_prompt=example_prompt,
    prefix="Give the antonym of every input",
    suffix="Input: {adjective}\nOutput:", 
    input_variables=["adjective"],
)

# 输入量极小,因此所有样本数据都会被选中
print(dynamic_prompt.format(adjective="big"))
```

你应该能看到如下输出:
```shell
Give the antonym of every input

Input: happy
Output: sad

Input: tall
Output: short

Input: energetic
Output: lethargic

Input: sunny
Output: gloomy

Input: windy
Output: calm

Input: big
Output:
```

注,选择器实例化时,我们没有改变 `get_text_length` 函数实现,其默认实现为:

```python
    def _get_length_based(text: str) -> int:
        return len(re.split("\n| ", text))

    ......

    get_text_length: Callable[[str], int] = _get_length_based
    """Function to measure prompt length. Defaults to word count."""
```

## 总结
本节课程中,我们简要介绍了LLM中的重要概念 `提示词` 并学习了如何使用 `Langchain` 的重要组件 `提示词模板`。

### 相关文档资料链接:
1. [Python Langchain官方文档](https://python.langchain.com/docs/get_started/introduction.html) 

================================================
FILE: 05_Output_Parsers/05_Output_Parsers.ipynb
================================================
{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": [],
      "authorship_tag": "ABX9TyMUPkO2WTAkNP76DCPwLqi1",
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/sugarforever/wtf-langchain/blob/main/05_Output_Parsers/05_Output_Parsers.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# 05 输出解析器\n",
        "\n",
        "LLM的输出为文本,但在程序中除了显示文本,可能希望获得更结构化的数据。这就是输出解析器(Output Parsers)的用武之地。"
      ],
      "metadata": {
        "id": "IkIusM-GD9MR"
      }
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Ceq3MMqkDutF",
        "outputId": "4c479341-e009-4997-cd66-f543d41df4e7"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\u001b[?25l     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m0.0/73.6 kB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m73.6/73.6 kB\u001b[0m \u001b[31m2.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25h"
          ]
        }
      ],
      "source": [
        "!pip install -q langchain==0.0.235 openai"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## List Parser\n",
        "\n",
        "List Parser将逗号分隔的文本解析为列表。"
      ],
      "metadata": {
        "id": "-0vNCtPT4zC6"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.output_parsers import CommaSeparatedListOutputParser\n",
        "\n",
        "output_parser = CommaSeparatedListOutputParser()\n",
        "output_parser.parse(\"black, yellow, red, green, white, blue\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "CUQ6R0V740yX",
        "outputId": "0adbbd93-a090-4770-e17b-31d58e5c4766"
      },
      "execution_count": 9,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "['black', 'yellow', 'red', 'green', 'white', 'blue']"
            ]
          },
          "metadata": {},
          "execution_count": 9
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Structured Output Parser\n",
        "\n",
        "当我们想要类似JSON数据结构,包含多个字段时,可以使用这个输出解析器。该解析器可以生成指令帮助LLM返回结构化数据文本,同时完成文本到结构化数据的解析工作。"
      ],
      "metadata": {
        "id": "KK4suFQr468t"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.output_parsers import StructuredOutputParser, ResponseSchema\n",
        "from langchain.prompts import PromptTemplate, ChatPromptTemplate, HumanMessagePromptTemplate\n",
        "from langchain.llms import OpenAI\n",
        "\n",
        "# 定义响应的结构(JSON),两个字段 answer和source。\n",
        "response_schemas = [\n",
        "    ResponseSchema(name=\"answer\", description=\"answer to the user's question\"),\n",
        "    ResponseSchema(name=\"source\", description=\"source referred to answer the user's question, should be a website.\")\n",
        "]\n",
        "output_parser = StructuredOutputParser.from_response_schemas(response_schemas)\n",
        "\n",
        "# 获取响应格式化的指令\n",
        "format_instructions = output_parser.get_format_instructions()\n",
        "\n",
        "format_instructions"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 54
        },
        "id": "Ohc2qbl05A7s",
        "outputId": "87aa976c-f11e-4f78-a9af-698b8e96347a"
      },
      "execution_count": 10,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "'The output should be a markdown code snippet formatted in the following schema, including the leading and trailing \"```json\" and \"```\":\\n\\n```json\\n{\\n\\t\"answer\": string  // answer to the user\\'s question\\n\\t\"source\": string  // source referred to answer the user\\'s question, should be a website.\\n}\\n```'"
            ],
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            }
          },
          "metadata": {},
          "execution_count": 10
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# partial_variables允许在代码中预填充提示此模版的部分变量。这类似于接口,抽象类之间的关系\n",
        "prompt = PromptTemplate(\n",
        "    template=\"answer the users question as best as possible.\\n{format_instructions}\\n{question}\",\n",
        "    input_variables=[\"question\"],\n",
        "    partial_variables={\"format_instructions\": format_instructions}\n",
        ")\n",
        "\n",
        "model = OpenAI(temperature=0, openai_api_key=\"您的有效openai api key\")\n",
        "response = prompt.format_prompt(question=\"Who is the CEO of Tesla?\")\n",
        "output = model(response.to_string())\n",
        "output_parser.parse(output)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "zxYWexu25Iuc",
        "outputId": "6886b6fa-feb5-4456-a9b6-554195a7d761"
      },
      "execution_count": 14,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'answer': 'Elon Musk is the CEO of Tesla.',\n",
              " 'source': 'https://www.tesla.com/about/leadership'}"
            ]
          },
          "metadata": {},
          "execution_count": 14
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 自定义输出解析器\n",
        "\n",
        "扩展CommaSeparatedListOutputParser,让其返回的列表是经过排序的。"
      ],
      "metadata": {
        "id": "7qYA8JPv6Pnz"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from typing import List\n",
        "class SortedCommaSeparatedListOutputParser(CommaSeparatedListOutputParser):\n",
        "  def parse(self, text: str) -> List[str]:\n",
        "    lst = super().parse(text)\n",
        "    return sorted(lst)\n",
        "\n",
        "output_parser = SortedCommaSeparatedListOutputParser()\n",
        "output_parser.parse(\"black, yellow, red, green, white, blue\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "6M-py_2C6beo",
        "outputId": "922875ac-c93e-4ee6-fad0-74c052bd0b68"
      },
      "execution_count": 15,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "['black', 'blue', 'green', 'red', 'white', 'yellow']"
            ]
          },
          "metadata": {},
          "execution_count": 15
        }
      ]
    }
  ]
}

================================================
FILE: 05_Output_Parsers/README.md
================================================
---
title: 05. 输出解析器
tags:
  - openai
  - llm
  - langchain
---

# WTF Langchain极简入门: 05. 输出解析器

最近在学习Langchain框架,顺手写一个“WTF Langchain极简入门”,供小白们使用(编程大佬可以另找教程)。本教程默认以下前提:
- 使用Python版本的[Langchain](https://github.com/hwchase17/langchain)
- LLM使用OpenAI的模型
- Langchain目前还处于快速发展阶段,版本迭代频繁,为避免示例代码失效,本教程统一使用版本 **0.0.235**

根据Langchain的[代码约定](https://github.com/hwchase17/langchain/blob/v0.0.235/pyproject.toml#L14C1-L14C24),Python版本 ">=3.8.1,<4.0"。

推特:[@verysmallwoods](https://twitter.com/verysmallwoods)

所有代码和教程开源在github: [github.com/sugarforever/wtf-langchain](https://github.com/sugarforever/wtf-langchain)

-----

## 简介

LLM的输出为文本,但在程序中除了显示文本,可能希望获得更结构化的数据。这就是输出解析器(Output Parsers)的用武之地。

`LangChain` 为输出解析器提供了基础类 `BaseOutputParser`。不同的输出解析器都继承自该类。它们需要实现以下两个函数:
- **get_format_instructions**:返回指令指定LLM的输出该如何格式化,该函数在实现类中必须重写。基类中的函数实现如下:
```python
def get_format_instructions(self) -> str:
    """Instructions on how the LLM output should be formatted."""
    raise NotImplementedError
```
- **parse**:解析LLM的输出文本为特定的结构。函数签名如下:
```python
def parse(self, text: str) -> T
```

`BaseOutputParser` 还提供了如下函数供重载:
**parse_with_prompt**:基于提示词上下文解析LLM的输出文本为特定结构。该函数在基类中的实现为:
```python
def parse_with_prompt(self, completion: str, prompt: PromptValue) -> Any:
    """Parse the output of an LLM call with the input prompt for context."""
    return self.parse(completion)
```

## LangChain支持的输出解析器

LangChain框架提供了一系列解析器实现来满足应用在不同功能场景中的需求。它们包括且不局限于如下解析器:
- List parser
- Datetime parser
- Enum parser
- Auto-fixing parser
- Pydantic parser
- Retry parser
- Structured output parser

本讲介绍具有代表性的两款解析器的使用。

### List Parser

List Parser将逗号分隔的文本解析为列表。

```python
from langchain.output_parsers import CommaSeparatedListOutputParser

output_parser = CommaSeparatedListOutputParser()
output_parser.parse("black, yellow, red, green, white, blue")
```

你应该能看到如下输出:

```shell
['black', 'yellow', 'red', 'green', 'white', 'blue']
```

### Structured Output Parser

当我们想要类似JSON数据结构,包含多个字段时,可以使用这个输出解析器。该解析器可以生成指令帮助LLM返回结构化数据文本,同时完成文本到结构化数据的解析工作。示例代码如下:

```python
from langchain.output_parsers import StructuredOutputParser, ResponseSchema
from langchain.prompts import PromptTemplate, ChatPromptTemplate, HumanMessagePromptTemplate
from langchain.llms import OpenAI

# 定义响应的结构(JSON),两个字段 answer和source。
response_schemas = [
    ResponseSchema(name="answer", description="answer to the user's question"),
    ResponseSchema(name="source", description="source referred to answer the user's question, should be a website.")
]
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)

# 获取响应格式化的指令
format_instructions = output_parser.get_format_instructions()

# partial_variables允许在代码中预填充提示此模版的部分变量。这类似于接口,抽象类之间的关系
prompt = PromptTemplate(
    template="answer the users question as best as possible.\n{format_instructions}\n{question}",
    input_variables=["question"],
    partial_variables={"format_instructions": format_instructions}
)

model = OpenAI(temperature=0)
response = prompt.format_prompt(question="what's the capital of France?")
output = model(response.to_string())
output_parser.parse(output)
```

你应该期望能看到如下输出:
```shell
{
    'answer': 'Paris',
    'source': 'https://www.worldatlas.com/articles/what-is-the-capital-of-france.html'
}
```

注,关于示例代码中引用的 `partial_variables`,请参考[Partial - Prompt Templates](https://python.langchain.com/docs/modules/model_io/prompts/prompt_templates/partial)。

## 总结
本节课程中,我们学习了什么是 `输出解析器` ,LangChain所支持的常见解析器,以及如何使用常见的两款解析器 `List Parser` 和 `Structured Output Parser`。随着LangChain的发展,应该会有更多解析器出现。

### 相关文档资料链接:
1. [Python Langchain官方文档](https://python.langchain.com/docs/get_started/introduction.html) 
2. [Partial - Prompt Templates](https://python.langchain.com/docs/modules/model_io/prompts/prompt_templates/partial)

================================================
FILE: 06_Chains/06_Chains.ipynb
================================================
{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": [],
      "authorship_tag": "ABX9TyMjzTlk9UUE+H/AN/ASt+Te",
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/sugarforever/wtf-langchain/blob/main/06_Chains/06_Chains.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "xlgJ4rFm-CS6",
        "outputId": "34b4e603-900f-443c-e7cb-d91050575e29"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.3/1.3 MB\u001b[0m \u001b[31m6.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m73.6/73.6 kB\u001b[0m \u001b[31m5.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m90.0/90.0 kB\u001b[0m \u001b[31m7.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m49.4/49.4 kB\u001b[0m \u001b[31m846.9 kB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25h"
          ]
        }
      ],
      "source": [
        "!pip install langchain==0.0.235 openai --quiet"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.llms import OpenAI\n",
        "from langchain.prompts import PromptTemplate\n",
        "\n",
        "llm = OpenAI(temperature=0, openai_api_key=\"您的有效openai api key\")\n",
        "prompt = PromptTemplate(\n",
        "    input_variables=[\"color\"],\n",
        "    template=\"What is the hex code of color {color}?\",\n",
        ")"
      ],
      "metadata": {
        "id": "1ILWDRoJ-NmX"
      },
      "execution_count": 2,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.chains import LLMChain\n",
        "chain = LLMChain(llm=llm, prompt=prompt)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "wPxzrJm230v0",
        "outputId": "e850bb26-1bd8-4826-d05c-0435f223a047"
      },
      "execution_count": 4,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\n",
            "\n",
            "The hex code of color green is #00FF00.\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "print(chain.run(\"green\"))\n",
        "print(chain.run(\"cyan\"))\n",
        "print(chain.run(\"magento\"))"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "9VmPzVQk4Flb",
        "outputId": "f5fce095-de2c-47c8-a051-b024e16a92da"
      },
      "execution_count": 6,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\n",
            "\n",
            "The hex code of color green is #00FF00.\n",
            "\n",
            "\n",
            "The hex code of color cyan is #00FFFF.\n",
            "\n",
            "\n",
            "The hex code for the color Magento is #E13939.\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.chains import load_chain\n",
        "import os\n",
        "\n",
        "os.environ['OPENAI_API_KEY'] = \"您的有效openai api key\"\n",
        "chain = load_chain(\"lc://chains/llm-math/chain.json\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "3DxFcpAn_Oy7",
        "outputId": "e2337345-c47f-4daf-83ca-6a52674a5476"
      },
      "execution_count": 7,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "/usr/local/lib/python3.10/dist-packages/langchain/chains/llm_math/base.py:50: UserWarning: Directly instantiating an LLMMathChain with an llm is deprecated. Please instantiate with llm_chain argument or using the from_llm class method.\n",
            "  warnings.warn(\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "chain.run(\"whats the area of a circle with radius 2?\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 147
        },
        "id": "LPsc-9ec_amC",
        "outputId": "cd246745-afda-4d06-bfe5-7f963ccec5ad"
      },
      "execution_count": 8,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\n",
            "\n",
            "\u001b[1m> Entering new LLMMathChain chain...\u001b[0m\n",
            "whats the area of a circle with radius 2?\u001b[32;1m\u001b[1;3m\n",
            "Answer: 12.566370614359172\u001b[0m\n",
            "\u001b[1m> Finished chain.\u001b[0m\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "'Answer: 12.566370614359172'"
            ],
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            }
          },
          "metadata": {},
          "execution_count": 8
        }
      ]
    }
  ]
}

================================================
FILE: 06_Chains/README.md
================================================
---
title: 06. 链
tags:
  - openai
  - llm
  - langchain
---

# WTF Langchain极简入门: 06. 链

最近在学习Langchain框架,顺手写一个“WTF Langchain极简入门”,供小白们使用(编程大佬可以另找教程)。本教程默认以下前提:
- 使用Python版本的[Langchain](https://github.com/hwchase17/langchain)
- LLM使用OpenAI的模型
- Langchain目前还处于快速发展阶段,版本迭代频繁,为避免示例代码失效,本教程统一使用版本 **0.0.235**

根据Langchain的[代码约定](https://github.com/hwchase17/langchain/blob/v0.0.235/pyproject.toml#L14C1-L14C24),Python版本 ">=3.8.1,<4.0"。

推特:[@verysmallwoods](https://twitter.com/verysmallwoods)

所有代码和教程开源在github: [github.com/sugarforever/wtf-langchain](https://github.com/sugarforever/wtf-langchain)

-----

## 简介

单一的LLM对于简单的应用场景已经足够,但是更复杂的应用程序需要将LLM串联在一起,需要多LLM协同工作。

LangChain提出了 `链` 的概念,为这种“链式”应用程序提供了 **Chain** 接口。`Chain` 定义组件的调用序列,其中可以包括其他链。链大大简化复杂应用程序的实现,并使其模块化,这也使调试、维护和改进应用程序变得更容易。

## 最基础的链 LLMChain

作为极简教程,我们从最基础的概念,与组件开始。`LLMChain` 是 `LangChain` 中最基础的链。本课就从 `LLMChain` 开始,学习链的使用。

`LLMChain` 接受如下组件:
- LLM
- 提示词模版

`LLMChain` 返回LLM的回复。

在[第二讲](../02_Models/README.md)中我们学习了OpenAI LLM的使用。现在我们基于OpenAI LLM,利用 `LLMChain` 尝试构建自己第一个链。

1. 准备必要的组件

```python
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate

llm = OpenAI(temperature=0, openai_api_key="您的有效openai ai key")
prompt = PromptTemplate(
    input_variables=["color"],
    template="What is the hex code of color {color}?",
)
```

2. 基于组件创建 `LLMChain` 实例

我们要创建的链,基于提示词模版,提供基于颜色名字询问对应的16进制代码的能力。

```python
from langchain.chains import LLMChain
chain = LLMChain(llm=llm, prompt=prompt)
```

3. 基于链提问

现在我们利用创建的 `LLMChain` 实例提问。注意,提问中我们只需要提供第一步中创建的提示词模版变量的值。我们分别提问green,cyan,magento三种颜色的16进制代码。

```python
print(chain.run("green"))
print(chain.run("cyan"))
print(chain.run("magento"))
```

你应该期望如下输出:

```shell
The hex code of color green is #00FF00.
The hex code of color cyan is #00FFFF.
The hex code for the color Magento is #E13939.
```

## LangChainHub

[LangChainHub](https://github.com/hwchase17/langchain-hub) 收集并分享用于处理 `LangChain` 基本元素(提示词,链,和代理等)。

本讲,我们介绍 `LangChainHub` 中分享的链的使用。

### Hello World链

代码仓库:[https://github.com/hwchase17/langchain-hub/blob/master/chains/hello-world/](https://github.com/hwchase17/langchain-hub/blob/master/chains/hello-world/)

链定义:[https://github.com/hwchase17/langchain-hub/blob/master/chains/hello-world/chain.json](https://github.com/hwchase17/langchain-hub/blob/master/chains/hello-world/chain.json)

定义的原始内容:
```json
{
    "memory": null,
    "verbose": false,
    "prompt": {
        "input_variables": [
            "topic"
        ],
        "output_parser": null,
        "template": "Tell me a joke about {topic}:",
        "template_format": "f-string",
        "_type": "prompt"
    },
    "llm": {
        "model_name": "text-davinci-003",
        "temperature": 0.9,
        "max_tokens": 256,
        "top_p": 1,
        "frequency_penalty": 0,
        "presence_penalty": 0,
        "n": 1,
        "best_of": 1,
        "request_timeout": null,
        "logit_bias": {},
        "_type": "openai"
    },
    "output_key": "text",
    "_type": "llm_chain"
}
```

这条链,使用如下组件:
1. 提示词模版 - 请求LLM回答一个 `topic` 参数指定的话题的笑话
2. LLM - OpenAI的 `text-davince-003` 模型(包括模型相关参数的设置)

### 从LangChainHub加载链

本课以链[LLM-Math](https://github.com/hwchase17/langchain-hub/tree/master/chains/llm-math)为例,介绍如何从 `LangChainHub` 加载链并使用它。这是一个使用LLM和Python REPL来解决复杂数学问题的链。

#### 加载

使用 `load_chain` 函数从hub加载。

```python
from langchain.chains import load_chain
import os

os.environ['OPENAI_API_KEY'] = "您的有效openai api key"
chain = load_chain("lc://chains/llm-math/chain.json")
```

注:
1. OpenAI类允许通过参数 `openai_api_key` 指定API Key,也可以通过环境变量 `OPENAI_API_KEY` 自动加载。在本例中,load_chain函数完成加载,OpenAI的实例化由框架完成,因此在这里我们用了环境变量来指定API Key。
2. `load_chain` 函数的参数是hub中分享的链的json定义。参数格式:`lc://<链json文件在LangChainHub的相对路径>`。

#### 提问

现在我们可以基于这个链提问。

```python
chain.run("whats the area of a circle with radius 2?")
```

你应该期望如下输出:

```shell
> Entering new LLMMathChain chain...
whats the area of a circle with radius 2?
Answer: 12.566370614359172
> Finished chain.

Answer: 12.566370614359172
```

## 总结
本节课程中,我们学习了`LangChain` 提出的最重要的概念 - 链(`Chain`) ,介绍了如何使用链,并分享了如何利用开源社区的力量 - 从 `LangChainHub` 加载链,让LLM开发变得更加轻松。

### 相关文档资料链接:
1. [Python Langchain官方文档](https://python.langchain.com/docs/get_started/introduction.html) 
2. [LangChain Hub](https://github.com/hwchase17/langchain-hub)

================================================
FILE: 07_Memory/07_Memory.ipynb
================================================
{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": [],
      "authorship_tag": "ABX9TyMQMnzD1ZmFkrax2yUbuaNc",
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/sugarforever/wtf-langchain/blob/main/07_Memory/07_Memory.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "import os\n",
        "\n",
        "os.environ['OPENAI_API_KEY'] = ''"
      ],
      "metadata": {
        "id": "_b-_WhYBeEJO"
      },
      "execution_count": 1,
      "outputs": []
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "xlgJ4rFm-CS6",
        "outputId": "288377e7-39d0-43e3-c133-76528dc66f3d"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.3/1.3 MB\u001b[0m \u001b[31m10.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m90.0/90.0 kB\u001b[0m \u001b[31m9.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m3.1/3.1 MB\u001b[0m \u001b[31m21.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m49.4/49.4 kB\u001b[0m \u001b[31m5.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25h"
          ]
        }
      ],
      "source": [
        "!pip install langchain==0.0.235 --quiet"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.memory import ConversationBufferMemory"
      ],
      "metadata": {
        "id": "1ILWDRoJ-NmX"
      },
      "execution_count": 3,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "memory = ConversationBufferMemory()\n",
        "memory.save_context({\"input\": \"hi\"}, {\"output\": \"whats up\"})"
      ],
      "metadata": {
        "id": "Blhn900A-SI9"
      },
      "execution_count": 4,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "memory.chat_memory.messages"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "gcsHB8Fa_fpq",
        "outputId": "635f1dd6-9c2e-4e0c-aeba-7b77c99f4176"
      },
      "execution_count": 5,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[HumanMessage(content='hi', additional_kwargs={}, example=False),\n",
              " AIMessage(content='whats up', additional_kwargs={}, example=False)]"
            ]
          },
          "metadata": {},
          "execution_count": 5
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "memory.load_memory_variables({})"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "dz-T1nKS-Wa8",
        "outputId": "ca78781f-bcbb-4d9e-97df-a189e2e2e43f"
      },
      "execution_count": 6,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'history': 'Human: hi\\nAI: whats up'}"
            ]
          },
          "metadata": {},
          "execution_count": 6
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "memory = ConversationBufferMemory(return_messages=True)\n",
        "memory.save_context({\"input\": \"hi\"}, {\"output\": \"whats up\"})"
      ],
      "metadata": {
        "id": "FDXjUwRk-YkS"
      },
      "execution_count": 7,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "memory.load_memory_variables({})"
      ],
      "metadata": {
        "id": "9xbrG4Vm-cnG"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.memory import ConversationBufferWindowMemory"
      ],
      "metadata": {
        "id": "qkJ4tGlGCOgm"
      },
      "execution_count": 8,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "memory = ConversationBufferWindowMemory( k=1)\n",
        "memory.save_context({\"input\": \"Hi, LangChain!\"}, {\"output\": \"Hey!\"})\n",
        "memory.save_context({\"input\": \"Where are you?\"}, {\"output\": \"By your side\"})"
      ],
      "metadata": {
        "id": "vMuMG8yzCRDE"
      },
      "execution_count": 9,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "memory.load_memory_variables({})"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "lDSZ8psOCUgn",
        "outputId": "654dc0e9-5d7b-4b58-8ca2-8124ae5d3006"
      },
      "execution_count": 10,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'history': 'Human: Where are you?\\nAI: By your side'}"
            ]
          },
          "metadata": {},
          "execution_count": 10
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "memory.chat_memory.messages"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "GDSbCvruCWOb",
        "outputId": "fc570f38-bcc9-4cff-806b-c463041d6477"
      },
      "execution_count": 11,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[HumanMessage(content='Hi, LangChain!', additional_kwargs={}, example=False),\n",
              " AIMessage(content='Hey!', additional_kwargs={}, example=False),\n",
              " HumanMessage(content='Where are you?', additional_kwargs={}, example=False),\n",
              " AIMessage(content='By your side', additional_kwargs={}, example=False)]"
            ]
          },
          "metadata": {},
          "execution_count": 11
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "!pip install openai"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "mSH0l-K6vDjW",
        "outputId": "3a5c5ebb-c350-40c7-e99c-8736f2d06ae9"
      },
      "execution_count": 18,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Requirement already satisfied: openai in /usr/local/lib/python3.10/dist-packages (0.27.10)\n",
            "Requirement already satisfied: requests>=2.20 in /usr/local/lib/python3.10/dist-packages (from openai) (2.31.0)\n",
            "Requirement already satisfied: tqdm in /usr/local/lib/python3.10/dist-packages (from openai) (4.66.1)\n",
            "Requirement already satisfied: aiohttp in /usr/local/lib/python3.10/dist-packages (from openai) (3.8.5)\n",
            "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests>=2.20->openai) (3.2.0)\n",
            "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests>=2.20->openai) (3.4)\n",
            "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests>=2.20->openai) (2.0.4)\n",
            "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests>=2.20->openai) (2023.7.22)\n",
            "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp->openai) (23.1.0)\n",
            "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp->openai) (6.0.4)\n",
            "Requirement already satisfied: async-timeout<5.0,>=4.0.0a3 in /usr/local/lib/python3.10/dist-packages (from aiohttp->openai) (4.0.3)\n",
            "Requirement already satisfied: yarl<2.0,>=1.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp->openai) (1.9.2)\n",
            "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp->openai) (1.4.0)\n",
            "Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp->openai) (1.3.1)\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.memory import ConversationSummaryMemory, ChatMessageHistory\n",
        "from langchain.llms import OpenAI\n",
        "from langchain.prompts import PromptTemplate\n",
        "from langchain.chains import LLMChain\n",
        "\n",
        "template = \"\"\"You are a chatbot having a conversation with a human.\n",
        "\n",
        "{conversation_history}\n",
        "\n",
        "Human: {input}\n",
        "Chatbot:\"\"\"\n",
        "\n",
        "prompt = PromptTemplate(\n",
        "    input_variables=[\"conversation_history\", \"input\"], template=template\n",
        ")\n",
        "memory = ConversationBufferMemory(memory_key=\"conversation_history\")\n",
        "\n",
        "llm_chain = LLMChain(\n",
        "    llm=OpenAI(),\n",
        "    prompt=prompt,\n",
        "    verbose=True,\n",
        "    memory=memory,\n",
        ")"
      ],
      "metadata": {
        "id": "I6cFKiW4vISf"
      },
      "execution_count": 19,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "llm_chain.predict(input=\"Where is Paris?\")"
      ],
      "metadata": {
        "id": "HYZT0RQ1l3DF",
        "outputId": "66854d0c-3b5f-4635-c0d8-8f8130009ea9",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 256
        }
      },
      "execution_count": 20,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\n",
            "\n",
            "\u001b[1m> Entering new LLMChain chain...\u001b[0m\n",
            "Prompt after formatting:\n",
            "\u001b[32;1m\u001b[1;3mYou are a chatbot having a conversation with a human.\n",
            "\n",
            "\n",
            "\n",
            "Human: Where is Paris?\n",
            "Chatbot:\u001b[0m\n",
            "\n",
            "\u001b[1m> Finished chain.\u001b[0m\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "' Paris is the capital and most populous city of France. It is located on the Seine River, in northern France, at the heart of the Île-de-France region.'"
            ],
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            }
          },
          "metadata": {},
          "execution_count": 20
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "llm_chain.predict(input=\"What did I just ask?\")"
      ],
      "metadata": {
        "id": "fexSvRR5l8BD",
        "outputId": "c4b9fbaa-4a1f-4b9d-9ea1-4ae22c19ffcf",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 275
        }
      },
      "execution_count": 21,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\n",
            "\n",
            "\u001b[1m> Entering new LLMChain chain...\u001b[0m\n",
            "Prompt after formatting:\n",
            "\u001b[32;1m\u001b[1;3mYou are a chatbot having a conversation with a human.\n",
            "\n",
            "Human: Where is Paris?\n",
            "AI:  Paris is the capital and most populous city of France. It is located on the Seine River, in northern France, at the heart of the Île-de-France region.\n",
            "\n",
            "Human: What did I just ask?\n",
            "Chatbot:\u001b[0m\n",
            "\n",
            "\u001b[1m> Finished chain.\u001b[0m\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "' You asked me where Paris is.'"
            ],
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            }
          },
          "metadata": {},
          "execution_count": 21
        }
      ]
    }
  ]
}

================================================
FILE: 07_Memory/README.md
================================================
---
title: 07. 记忆组件
tags:
  - openai
  - llm
  - langchain
---

# WTF Langchain极简入门: 07. 记忆组件

最近在学习Langchain框架,顺手写一个“WTF Langchain极简入门”,供小白们使用(编程大佬可以另找教程)。本教程默认以下前提:
- 使用Python版本的[Langchain](https://github.com/hwchase17/langchain)
- LLM使用OpenAI的模型
- Langchain目前还处于快速发展阶段,版本迭代频繁,为避免示例代码失效,本教程统一使用版本 **0.0.235**

根据Langchain的[代码约定](https://github.com/hwchase17/langchain/blob/v0.0.235/pyproject.toml#L14C1-L14C24),Python版本 ">=3.8.1,<4.0"。

推特:[@verysmallwoods](https://twitter.com/verysmallwoods)

所有代码和教程开源在github: [github.com/sugarforever/wtf-langchain](https://github.com/sugarforever/wtf-langchain)

-----

## 简介

大多数LLM应用都具有对话界面。对话的一个重要组成部分是对话历史中的信息。我们将这种存储对话历史中的信息的能力称为"记忆"。`LangChain` 提供了一系列记忆相关的实用工具。这些工具可以单独使用,也可以无缝地集成到一条链中。

记忆组件需要支持
- 读取
- 写入

注,每条链定义了核心执行逻辑,期望某些输入。一些输入来自用户,另一些可能来自记忆组件。在一次与LLM的交互中,链将与记忆组件交互两次:
1. 接收到初始用户输入之后,执行核心逻辑之前,链从记忆组件读取历史,并以此增强用户输入。
2. 执行核心逻辑之后,在返回回答之前,链把当前交互的输入和输出写入到记忆中,以便更新对话历史。

## LangChain的记忆组件类型

记忆组件需要解决两大问题:
1. 历史如何存储?
2. 历史如何查询?

本讲通过 `LangChain` 提供的三种基本记忆组件类型 `ConversationBufferMemory`,`ConversationBufferWindowMemory`,`ConversationSummaryMemory`,介绍它们对于上述问题的解决方案,并分享使用方法。

### ConversationBufferMemory

`ConversationBufferMemory` 是 `LangChain` 提供的记忆组件类, 它如实地在列表中记录对话历史消息。

#### 写入一次对话

通过 `save_context` 函数来保存用户输入和模型输出。

```python
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory()
memory.save_context({"input": "Hi, LangChain!"}, {"output": "Hey!"})
```

`ConversationBufferMemory` 的 `chat_memory` 成员变量有一个 `messages` 变量。这是一个消息数组。通过如下代码查看消息对象列表

```python
memory.chat_memory.messages
```

你应该期望看到如下输出:

```shell
[HumanMessage(content='Hi, LangChain!', additional_kwargs={}, example=False),
 AIMessage(content='Hey!', additional_kwargs={}, example=False)]
```

当我们需要生成对话历史的文本,作为变量嵌入提示词,可以通过调用函数 `load_memory_variables` 获得字典对象,其中的键 `history` 包含了对话历史的字符串值。如下:

```python
memory.load_memory_variables({})
```

你应该期望看到如下输出:

```shell
{'history': 'Human: Hi, LangChain!\nAI: Hey!'}
```

`ConversationBufferMemory` 的实现方式简单,在交互次数少,输入输出字符量不大的情况下,非常有效。但是当交互增加,字符数量增多,对话历史的字符数可能导致增强后的提示词tokens数超过上下文限制,最终导致模型调用失败。因此,`LangChain` 还提供了其他记忆组件类型。

### ConversationBufferWindowMemory

`ConversationBufferWindowMemory` 持续记录对话历史,但只使用最近的K个交互。这种滑动窗口的机制,确保缓存大小不会变得过大。

用法如下:

我们指定滑动窗口的大小为1,表示查询时只返回最近1次交互。

```python
memory = ConversationBufferWindowMemory( k=1)
memory.save_context({"input": "Hi, LangChain!"}, {"output": "Hey!"})
memory.save_context({"input": "Where are you?"}, {"output": "By your side"})
```

通过 `load_memory_variables` 读取记忆

```python
memory.load_memory_variables({})
```

你应该期望看到如下输出:

```shell
{'history': 'Human: Where are you?\nAI: By your side'}
```

我们看看记忆组件中存储的历史交互:

```python
memory.chat_memory.messages
```

输出:

```shell
[HumanMessage(content='Hi, LangChain!', additional_kwargs={}, example=False),
 AIMessage(content='Hey!', additional_kwargs={}, example=False),
 HumanMessage(content='Where are you?', additional_kwargs={}, example=False),
 AIMessage(content='By your side', additional_kwargs={}, example=False)]
```

可见,组件记忆了所有交互,但是在查询时通过滑动窗口返回指定数量的交互(输入与输出)。

### ConversationSummaryMemory

`ConversationSummaryMemory` 是稍微复杂的记忆类型。这种记忆随着时间的推移总结对话的内容,并将当前的摘要存储在记忆中,然后在需要的时候将对话摘要注入提示词或链中。`ConversationSummaryMemory` 对于更长的对话交互很有用,因为将过去的历史记录逐字逐句放入提示词中会占用太多Token。

注意,由于需要对于对话历史进行总结,生成摘要,因此 `ConversationSummaryMemory` 需要LLM的配合。我们在示例代码中将提供OpenAI的模型给 `ConversationSummaryMemory` 以生成摘要。

用法如下:

```python
from langchain.memory import ConversationSummaryMemory
from langchain.llms import OpenAI

memory = ConversationSummaryMemory(llm=OpenAI(temperature=0, openai_api_key="您的有效openai api key"))
memory.save_context({"input": "Hi, LangChain!"}, {"output": "Hey!"})
memory.save_context({"input": "How to start with Next.js development?"}, {"output": "You can get started with its official developer guide."})
memory.save_context({"input": "Show me the link of the guide."}, {"output": "I'm looking for you now. Please stand by!"})

memory.load_memory_variables({})
```

你应该能看到如下输出:

```shell
{'history': '\nThe human greets the AI, LangChain, to which the AI responds with a friendly "Hey!" The human then asks how to start with Next.js development, to which the AI responds with instructions to use the official developer guide, and provides a link when asked.'}
```

你可能注意到了,从记忆组件中得到的对话历史的文本,相较于原始的对话文字,并没有显著地缩短。原因在于对话的交互只有3次,在这种情况下,摘要的优势并没有显示出来。

下图是不同记忆类型组件随着对话交互的增加,生成的对话历史信息的Token开销趋势。
可见,`ConversationSummaryMemory` 的Token开销相对平缓,这对于交互多的对话是更有效的。

![不同类型记忆的Token开销](./memory_types_performance.png)

图中,还展示了我们并没有介绍的类型 `Summary Buffer Memory`。顾名思义,这是结合了 `Summary` 和 `Buffer` 的优势的一种记忆类型。

## 总结
本节课程中,我们学习了什么是 `记忆组件` ,并通过三种基本记忆组件类型 `ConversationBufferMemory`,`ConversationBufferWindowMemory`,`ConversationSummaryMemory`,介绍它们的工作原理和使用方法。本课只介绍了 `LangChain` 提供的部分记忆组件,更多类型请参考官方文档 [Memory Types](https://python.langchain.com/docs/modules/memory/types/)。

### 相关文档资料链接:
1. [Python Langchain官方文档](https://python.langchain.com/docs/get_started/introduction.html) 
2. [记忆组件](https://python.langchain.com/docs/modules/memory/)

================================================
FILE: 08_Agents/08_Agents.ipynb
================================================
{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": [],
      "authorship_tag": "ABX9TyMl2q/nwBstRfIi1uOWucfB",
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/sugarforever/wtf-langchain/blob/main/08_Agents/08_Agents.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# LangChain Agent 示例"
      ],
      "metadata": {
        "id": "GIUvp6uXWAcn"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 准备环境"
      ],
      "metadata": {
        "id": "h4Ps0wdmWFGh"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "1. 安装langchain版本0.0.235"
      ],
      "metadata": {
        "id": "E8OKcwR6VdHL"
      }
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "metadata": {
        "id": "yDa7VdF9SlAm"
      },
      "outputs": [],
      "source": [
        "!pip install -q -U langchain==0.0.235"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "2. 安装duckduckgo-search包\n",
        "  \n",
        "  在使用DuckDuckGoSearchRun这个类时会需要这个python包。"
      ],
      "metadata": {
        "id": "WgipamGdVnYL"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "!pip install -q -U duckduckgo-search"
      ],
      "metadata": {
        "id": "faW8Qfw1TJaf"
      },
      "execution_count": 17,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 示例代码"
      ],
      "metadata": {
        "id": "i0wXx4wfWIjF"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Tool"
      ],
      "metadata": {
        "id": "lEPqka5QWcph"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "1. 通过工具类创建工具实例"
      ],
      "metadata": {
        "id": "xnhIUB4rVTmZ"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.tools import DuckDuckGoSearchRun\n",
        "\n",
        "search = DuckDuckGoSearchRun()\n",
        "search.run(\"Who is winner of FIFA worldcup 2018?\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 202
        },
        "id": "u7Qj56OFTMus",
        "outputId": "f011f0da-5687-4978-cbc6-bcf2e5fd0930"
      },
      "execution_count": 7,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "\"The 2018 FIFA World Cup was the 21st FIFA World Cup, the quadrennial world championship for national football teams organized by FIFA.It took place in Russia from 14 June to 15 July 2018, after the country was awarded the hosting rights in 2010. It was the eleventh time the championships had been held in Europe, and the first time they were held in Eastern Europe. Who won the last World Cup at Russia 2018? Revisiting France's championship run | Sporting News EVE LEI 12:00p +100 CHE ARS 12:00p -160 WHU MCI 12:00p -475 FUL LIV 12:00p -360 MUN BHA Thu,... World Cup 2018: The Winners and Losers. Luka Modric and Kylian Mbappe during the World Cup final. Jewel Samad/Agence France-Presse — Getty Images. France won the World Cup, but that is far from ... Brazil have won it five times, while Germany and Italy have four titles each. Argentina, France and Uruguay have each won the World Cup twice, while England and Spain have each won it once. Post... Didier Deschamps was the manager of the French team that won the 2018 FIFA World Cup, making him the third individual to win the title both as a player and a manager. Mario Zagallo (Brazil) and Franz Beckenbauer (Germany) have also achieved the feat.\""
            ],
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            }
          },
          "metadata": {},
          "execution_count": 7
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "2. 通过辅助函数 `load_tools` 加载"
      ],
      "metadata": {
        "id": "8yK-vVhIVV8L"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.agents import load_tools\n",
        "\n",
        "tools = load_tools(['ddg-search'])\n",
        "search = tools[0]\n",
        "search.run(\"Who is winner of FIFA worldcup 2018?\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 202
        },
        "id": "gHfGofDRV4Nn",
        "outputId": "22e3433d-38fb-4d7f-ef4e-c4d7a16b2c89"
      },
      "execution_count": 11,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "\"The 2018 FIFA World Cup was the 21st FIFA World Cup, the quadrennial world championship for national football teams organized by FIFA.It took place in Russia from 14 June to 15 July 2018, after the country was awarded the hosting rights in 2010. It was the eleventh time the championships had been held in Europe, and the first time they were held in Eastern Europe. FIFA World Cup Qatar 2022™. World Cup memories of a French superfan. 6 May 2020. 2018 FIFA World Cup Russia™. #WorldCupAtHome: Electric Mbappe helps France win seven-goal thriller. 9 Apr 2020 ... Who won the last World Cup at Russia 2018? Revisiting France's championship run | Sporting News EVE LEI 12:00p +100 CHE ARS 12:00p -160 WHU MCI 12:00p -475 FUL LIV 12:00p -360 MUN BHA Thu,... World Cup 2018: The Winners and Losers. Luka Modric and Kylian Mbappe during the World Cup final. Jewel Samad/Agence France-Presse — Getty Images. France won the World Cup, but that is far from ... Antoine Griezmann, Paul Pogba and Kylian Mbappé each scored in the final against their unexpected foe, Croatia. The 19-year-old Mbappé became the first teenager to score in a World Cup final...\""
            ],
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            }
          },
          "metadata": {},
          "execution_count": 11
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "3. 查看 `LangChain` 内置支持的工具列表"
      ],
      "metadata": {
        "id": "zuNvBf2uWSMv"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.agents import get_all_tool_names\n",
        "\n",
        "get_all_tool_names()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "pWjMoK-daWl8",
        "outputId": "8fc38cde-4038-44e3-cc06-8c9a2e870180"
      },
      "execution_count": 12,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "['python_repl',\n",
              " 'requests',\n",
              " 'requests_get',\n",
              " 'requests_post',\n",
              " 'requests_patch',\n",
              " 'requests_put',\n",
              " 'requests_delete',\n",
              " 'terminal',\n",
              " 'sleep',\n",
              " 'wolfram-alpha',\n",
              " 'google-search',\n",
              " 'google-search-results-json',\n",
              " 'searx-search-results-json',\n",
              " 'bing-search',\n",
              " 'metaphor-search',\n",
              " 'ddg-search',\n",
              " 'google-serper',\n",
              " 'google-serper-results-json',\n",
              " 'serpapi',\n",
              " 'twilio',\n",
              " 'searx-search',\n",
              " 'wikipedia',\n",
              " 'arxiv',\n",
              " 'pupmed',\n",
              " 'human',\n",
              " 'awslambda',\n",
              " 'sceneXplain',\n",
              " 'graphql',\n",
              " 'openweathermap-api',\n",
              " 'dataforseo-api-search',\n",
              " 'dataforseo-api-search-json',\n",
              " 'news-api',\n",
              " 'tmdb-api',\n",
              " 'podcast-api',\n",
              " 'pal-math',\n",
              " 'pal-colored-objects',\n",
              " 'llm-math',\n",
              " 'open-meteo-api']"
            ]
          },
          "metadata": {},
          "execution_count": 12
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Agent"
      ],
      "metadata": {
        "id": "IITv5SLpcNi8"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "#### 额外的环境准备\n",
        "\n",
        "这里我们会用到OpenAI的模型,因此安装python包 `openai` 。"
      ],
      "metadata": {
        "id": "X6fBRd3MWkvZ"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "!pip install -q -U openai"
      ],
      "metadata": {
        "id": "3EkfTNMEdLGl"
      },
      "execution_count": 18,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "一个 `Agent` 的例子"
      ],
      "metadata": {
        "id": "q8y0lMWIWt4m"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.agents import load_tools\n",
        "from langchain.agents import initialize_agent\n",
        "from langchain.agents import AgentType\n",
        "from langchain.llms import OpenAI\n",
        "\n",
        "import os\n",
        "os.environ['OPENAI_API_KEY'] = \"\"\n",
        "\n",
        "llm = OpenAI(temperature=0)\n",
        "tools = load_tools([\"ddg-search\", \"llm-math\"], llm=llm)\n",
        "agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)\n",
        "agent.run(\"What is the height difference between Eiffel Tower and Taiwan 101 Tower?\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 407
        },
        "id": "1rkToli6cSlT",
        "outputId": "447ab4e9-845e-4bfa-d173-09a04c95c21a"
      },
      "execution_count": 15,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\n",
            "\n",
            "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
            "\u001b[32;1m\u001b[1;3m I need to find out the heights of both towers.\n",
            "Action: duckduckgo_search\n",
            "Action Input: \"Eiffel Tower height\"\u001b[0m\n",
            "Observation: \u001b[36;1m\u001b[1;3mIt was the first structure in the world to surpass both the 200-metre and 300-metre mark in height. Due to the addition of a broadcasting aerial at the top of the tower in 1957, it is now taller than the Chrysler Building by 5.2 metres (17 ft). Eiffel's concept of a 300-metre (984-foot) tower built almost entirely of open-lattice wrought iron aroused amazement, skepticism, and no little opposition on aesthetic grounds. When completed, the tower served as the entrance gateway to the exposition. Built for the 1889 World's Fair in Paris, the Eiffel Tower is a 1,000-foot tall wrought iron tower, considered an architectural wonder and one of the world's most recognizable structures. The Eiffel Tower reached the height of 330 meters (1,083 feet) on March 15, 2022 thanks to this successful and dizzying operation carried out by helicopter. Which was a first ! Browse our resources on the links between the Tower and radio. Discover how did radio save the Tower. All about the Tower's Scientific Uses Height of the Eiffel Tower. The main structure of the is Eiffel Tower is 300 meters tall, although the height including antennas is 324 meters. This height is roughly equivalent to that of an 81-storey building. Interestingly, the Eiffel Tower can shrink by 6 inches during cold temperatures. The tower has a square base that measures 125 meters ...\u001b[0m\n",
            "Thought:\u001b[32;1m\u001b[1;3m I need to find out the height of Taiwan 101 Tower.\n",
            "Action: duckduckgo_search\n",
            "Action Input: \"Taiwan 101 Tower height\"\u001b[0m\n",
            "Observation: \u001b[36;1m\u001b[1;3mDesigned by C.Y. Lee & Partners, a local architectural firm, the skyscraper has 101 stories and reaches a height, including the spire, of 1,667 feet (508 metres). At the time of its official opening in October 2004, it was the world's tallest building, having surpassed the Petronas Twin Towers in Kuala Lumpur, Malaysia. Taipei 101 is the tallest building in Taiwan. The elevators of Taipei 101 that transport passengers from the 5th to the 89th floor in 37 seconds (attaining 60.6 km/h ... The height of 101 floors commemorates the renewal of time: the new century that arrived as the tower was built (100+1) and all the new years that follow (1 January = 1-01 ... Last updated: Sep 1, 2021 • 3 min read Taipei 101, located in Taiwan, is one of the tallest high-rise buildings in the world. Visitors can explore the building that includes a large shopping mall, world-class restaurants, and an outdoor observation deck. Learn From the Best Food Design & Style Arts & Entertainment Music Business Sports & Gaming With a height of 1667 feet or 508 meters, Taipei 101 has the distinction of being the third tallest building in the world, and tallest outside the Middle East. Its spire adds to its height, and the total makes it higher than the Kuala Lumpur Petronas Twin Towers. The Taipei 101 tower in Taipei, Taiwan, was the world's tallest building from 2004 until 2010 when it was beaten out by Dubai's impressive Burj Khalifa. Regardless, Taipei 101 is still considered the tallest green building in the world for its innovative and energy-saving design. Even the 2015-2016 New Year's Eve firework's show was nature ...\u001b[0m\n",
            "Thought:\u001b[32;1m\u001b[1;3m I now know the heights of both towers, so I can calculate the difference.\n",
            "Action: Calculator\n",
            "Action Input: 330 - 508\u001b[0m\n",
            "Observation: \u001b[33;1m\u001b[1;3mAnswer: -178\u001b[0m\n",
            "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer.\n",
            "Final Answer: The height difference between Eiffel Tower and Taiwan 101 Tower is 178 meters.\u001b[0m\n",
            "\n",
            "\u001b[1m> Finished chain.\u001b[0m\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "'The height difference between Eiffel Tower and Taiwan 101 Tower is 178 meters.'"
            ],
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            }
          },
          "metadata": {},
          "execution_count": 15
        }
      ]
    }
  ]
}

================================================
FILE: 08_Agents/README.md
================================================
---
title: 08. 代理 (Agent)
tags:
  - openai
  - llm
  - langchain
---

# WTF Langchain极简入门: 08. 代理 (Agent)

最近在学习Langchain框架,顺手写一个“WTF Langchain极简入门”,供小白们使用(编程大佬可以另找教程)。本教程默认以下前提:
- 使用Python版本的[Langchain](https://github.com/hwchase17/langchain)
- LLM使用OpenAI的模型
- Langchain目前还处于快速发展阶段,版本迭代频繁,为避免示例代码失效,本教程统一使用版本 **0.0.235**

根据Langchain的[代码约定](https://github.com/hwchase17/langchain/blob/v0.0.235/pyproject.toml#L14C1-L14C24),Python版本 ">=3.8.1,<4.0"。

推特:[@verysmallwoods](https://twitter.com/verysmallwoods)

所有代码和教程开源在github: [github.com/sugarforever/wtf-langchain](https://github.com/sugarforever/wtf-langchain)

-----

## 简介

`Agent` 也就是 代理,它的核心思想是利用一个语言模型来选择一系列要执行的动作。`LangChain` 的链将一系列的动作硬编码在代码中。而在 `Agent` 中,语言模型被用作推理引擎,来确定应该执行哪些动作以及以何种顺序执行。

这就涉及到几个关键组件:

- `Agent` 代理
- `Tool` 工具
- `Toolkit` 工具包
- `AgentExecutor` 代理执行器

接下来我们做逐一介绍。注,该极简入门系列将略过工具包的介绍,这部分内容将包含在进阶系列中。

## Agent

`Agent` 由一个语言模型和一个提示词驱动,决定下一步要采取什么措施的类。提示词可以包括以下内容:

- 代理的个性(用于使其以特定方式回应)
- 代理的背景(用于为其提供更多关于所要执行任务类型的上下文信息)
- 引导策略(用于激发更好的推理能力)

`LangChain` 提供了不同类型的代理:

- Zero-shot ReAct
    
    利用 ReAct 框架根据工具的描述来决定使用哪个工具,可以使用多个工具,但需要为每个工具提供描述信息。工具的选择单纯依靠工具的描述信息。关于 ReAct 框架的更多信息,请参考 [ReAct](https://arxiv.org/pdf/2205.00445.pdf)。

- Structured Input ReAct
    
    相较于单一字符串作为输入的代理,该类型的代理可以通过工具的参数schema创建结构化的动作输入。

- OpenAI Functions

    该类型的代理用来与OpenAI Function Call机制配合工作。

- Conversational

    这类代理专为对话场景设计,使用具有对话性的提示词,利用 ReAct 框架选择工具,并利用记忆功能来保存对话历史。

- Self ask with search

    这类代理利用工具查找问题的事实性答案。

- ReAct document store

    利用 ReAct 框架与文档存储进行交互,使用时需要提供 `Search` 工具和 `Lookup` 工具,分别用于搜索文档和在最近找到的文档中查找术语。

- Plan-and-execute agents

    代理规划要做的事情,然后执行子任务来达到目标。

这里我们多次提到 “工具”,也就是 `Tool`,接下来我们就介绍什么是 `Tool`。

## Tool

`Tool` 工具,是代理调用的功能,通常用来与外部世界交互,比如维基百科搜索,资料库访问等。`LangChain` 内置的工具列表,请参考 [Tools](https://python.langchain.com/docs/integrations/tools/)。

## Toolkit

通常,在达成特定目标时,需要使用一组工具。`LangChain` 提供了 `Toolkit` 工具包的概念,将多个工具组合在一起。

## AgentExecutor

代理执行器是代理的运行时。程序运行中,由它来调用代理并执行其选择的动作。

## 组件实例

### Tool

`LangChain` 提供了一系列工具,比如 `Search` 工具,`AWS` 工具,`Wikipedia` 工具等。这些工具都是 `BaseTool` 的子类。通过调用 `run` 函数,执行工具的功能。

我们以 `LangChain` 内置的工具 `DuckDuckGoSearchRun` 为例,来看看如何使用工具。

注,要使用DuckDuckGoSearchRun工具,需要安装以下python包:


```shell
pip install duckduckgo-search
```

1. 通过工具类创建工具实例

    该类提供了通过 [`DuckDuckGo`](https://duckduckgo.com/) 搜索引擎搜索的功能。

    ```python
    from langchain.tools import DuckDuckGoSearchRun
    search = DuckDuckGoSearchRun()
    search.run("Who is winner of FIFA worldcup 2018?")
    ```

    你应该期望如下输出:

    ```shell
    The 2018 FIFA World Cup was the 21st FIFA World Cup, ... Mario Zagallo (Brazil) and Franz Beckenbauer (Germany) have also achieved the feat.
    ```

    注,限于篇幅,这里对模型的回答文本在本讲中做了截取。

2. 通过辅助函数 `load_tools` 加载

    `LangChain` 提供了函数 `load_tools` 基于工具名称加载工具。

    先来看看DuckDuckGoSearchRun类的定义:

    ```python
    class DuckDuckGoSearchRun(BaseTool):
        """Tool that adds the capability to query the DuckDuckGo search API."""

        name = "duckduckgo_search"
        description = (
            "A wrapper around DuckDuckGo Search. "
            "Useful for when you need to answer questions about current events. "
            "Input should be a search query."
        )
    ```

    `name` 变量定义了工具的名称。这正是我们使用 `load_tools` 函数加载工具时所需要的。当然,目前比较棘手的是,`load_tools` 的实现对工具名称做了映射,因此并不是所有工具都如实使用工具类中定义的 `name`。比如,`DuckDuckGoSearchRun` 的名称是 `duckduckgo_search`,但是 `load_tools` 函数需要使用 `ddg-search` 来加载该工具。

    请参考源代码 [load_tools.py](https://github.com/langchain-ai/langchain/blob/v0.0.235/langchain/agents/load_tools.py#L314) 了解工具数据初始化的详情。

    用法

    ```python
    from langchain.agents import load_tools

    tools = load_tools(['ddg-search'])
    search = tools[0]
    search.run("Who is winner of FIFA worldcup 2018?")
    ```

    你应该期望与方法1类似的输出。

    最后,分享一个辅助函数 `get_all_tool_names`,用于获取所有工具的名称。
    
    ```python
    from langchain.agents import get_all_tool_names
    get_all_tool_names()
    ```

    当前 `LangChain` 版本 `0.0.235` 中,我们应该能看到如下列表:

    ```shell
       ['python_repl',
        'requests',
        'requests_get',
        'requests_post',
        'requests_patch',
        'requests_put',
        'requests_delete',
        'terminal',
        'sleep',
        'wolfram-alpha',
        'google-search',
        'google-search-results-json',
        'searx-search-results-json',
        'bing-search',
        'metaphor-search',
        'ddg-search',
        'google-serper',
        'google-serper-results-json',
        'serpapi',
        'twilio',
        'searx-search',
        'wikipedia',
        'arxiv',
        'pupmed',
        'human',
        'awslambda',
        'sceneXplain',
        'graphql',
        'openweathermap-api',
        'dataforseo-api-search',
        'dataforseo-api-search-json',
        'news-api',
        'tmdb-api',
        'podcast-api',
        'pal-math',
        'pal-colored-objects',
        'llm-math',
        'open-meteo-api']
    ```

### Agent

`Agent` 通常需要 `Tool` 配合工作,因此我们将 `Agent` 实例放在 `Tool` 之后。我们以 Zero-shot ReAct 类型的 `Agent` 为例,来看看如何使用。代码如下:

```python
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
from langchain.llms import OpenAI

import os
os.environ['OPENAI_API_KEY'] = "您的有效openai api key"

llm = OpenAI(temperature=0)
tools = load_tools(["ddg-search", "llm-math"], llm=llm)
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
agent.run("What is the height difference between Eiffel Tower and Taiwan 101 Tower?")
```

代码解释:
1. 设置环境变量 `OPENAI_API_KEY` 并实例化 `OpenAI` 语言模型,用于后续的推理。
2. 通过load_tools加载 `DuckDuckGo` 搜索工具和 `llm-math` 工具。
3. 通过 `initialize_agent` 函数初始化代理执行器,指定代理类型为 `ZERO_SHOT_REACT_DESCRIPTION`,并打开 `verbose` 模式,用于输出调试信息。
4. 通过 `run` 函数运行代理。

参考 `initialize_agent` 的实现,我们会看到它返回的是 `AgentExecutor` 类型的实例。这也是代理执行器的常见用法。请前往源代码 [initialize.py](https://github.com/langchain-ai/langchain/blob/v0.0.235/langchain/agents/initialize.py#L12) 了解更多初始化代理执行器的详情。

```python
def initialize_agent(
    tools: Sequence[BaseTool],
    llm: BaseLanguageModel,
    agent: Optional[AgentType] = None,
    callback_manager: Optional[BaseCallbackManager] = None,
    agent_path: Optional[str] = None,
    agent_kwargs: Optional[dict] = None,
    *,
    tags: Optional[Sequence[str]] = None,
    **kwargs: Any,
) -> AgentExecutor:
    """Load an agent executor given tools and LLM.
```

你应该期望如下输出:

```shell
> Entering new AgentExecutor chain...
 I need to find out the heights of both towers.
Action: duckduckgo_search
Action Input: "Eiffel Tower height"
Observation: It was the first structure in the world to surpass both the 200-metre and 300-metre mark in height. Due to the addition of a broadcasting aerial at the top of the tower in 1957, it is now taller than the Chrysler Building by 5.2 metres (17 ft). Eiffel's concept of a 300-metre (984-foot) tower built almost entirely of open-lattice wrought iron aroused amazement, skepticism, and no little opposition on aesthetic grounds. When completed, the tower served as the entrance gateway to the exposition. Built for the 1889 World's Fair in Paris, the Eiffel Tower is a 1,000-foot tall wrought iron tower, considered an architectural wonder and one of the world's most recognizable structures. The Eiffel Tower reached the height of 330 meters (1,083 feet) on March 15, 2022 thanks to this successful and dizzying operation carried out by helicopter. Which was a first ! Browse our resources on the links between the Tower and radio. Discover how did radio save the Tower. All about the Tower's Scientific Uses Height of the Eiffel Tower. The main structure of the is Eiffel Tower is 300 meters tall, although the height including antennas is 324 meters. This height is roughly equivalent to that of an 81-storey building. Interestingly, the Eiffel Tower can shrink by 6 inches during cold temperatures. The tower has a square base that measures 125 meters ...
Thought: I need to find out the height of Taiwan 101 Tower.
Action: duckduckgo_search
Action Input: "Taiwan 101 Tower height"
Observation: Designed by C.Y. Lee & Partners, a local architectural firm, the skyscraper has 101 stories and reaches a height, including the spire, of 1,667 feet (508 metres). At the time of its official opening in October 2004, it was the world's tallest building, having surpassed the Petronas Twin Towers in Kuala Lumpur, Malaysia. Taipei 101 is the tallest building in Taiwan. The elevators of Taipei 101 that transport passengers from the 5th to the 89th floor in 37 seconds (attaining 60.6 km/h ... The height of 101 floors commemorates the renewal of time: the new century that arrived as the tower was built (100+1) and all the new years that follow (1 January = 1-01 ... Last updated: Sep 1, 2021 • 3 min read Taipei 101, located in Taiwan, is one of the tallest high-rise buildings in the world. Visitors can explore the building that includes a large shopping mall, world-class restaurants, and an outdoor observation deck. Learn From the Best Food Design & Style Arts & Entertainment Music Business Sports & Gaming With a height of 1667 feet or 508 meters, Taipei 101 has the distinction of being the third tallest building in the world, and tallest outside the Middle East. Its spire adds to its height, and the total makes it higher than the Kuala Lumpur Petronas Twin Towers. The Taipei 101 tower in Taipei, Taiwan, was the world's tallest building from 2004 until 2010 when it was beaten out by Dubai's impressive Burj Khalifa. Regardless, Taipei 101 is still considered the tallest green building in the world for its innovative and energy-saving design. Even the 2015-2016 New Year's Eve firework's show was nature ...
Thought: I now know the heights of both towers, so I can calculate the difference.
Action: Calculator
Action Input: 330 - 508
Observation: Answer: -178
Thought: I now know the final answer.
Final Answer: The height difference between Eiffel Tower and Taiwan 101 Tower is 178 meters.

> Finished chain.

'The height difference between Eiffel Tower and Taiwan 101 Tower is 178 meters.'
```


## 总结

本节课程中,我们学习了什么是 `Agent` 代理,`Tool` 工具,以及 `AgentExecutor` 代理执行器,并学习了它们的基本用法。下一讲我们将学习 `Callback` 回调。

本节课程的完整示例代码,请参考 [08_Agents.ipynb](./08_Agents.ipynb)。

### 相关文档资料链接:
1. [Python Langchain官方文档](https://python.langchain.com/docs/get_started/introduction.html) 

================================================
FILE: 09_Callbacks/09_Callbacks.ipynb
================================================
{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": [],
      "authorship_tag": "ABX9TyMDQAGwDnmThXqSHdNwTwvq",
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/sugarforever/wtf-langchain/blob/main/09_Callbacks/09_Callbacks.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# LangChain Callback 示例"
      ],
      "metadata": {
        "id": "GIUvp6uXWAcn"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 准备环境"
      ],
      "metadata": {
        "id": "h4Ps0wdmWFGh"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "1. 安装langchain版本0.0.235,以及openai"
      ],
      "metadata": {
        "id": "E8OKcwR6VdHL"
      }
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "id": "yDa7VdF9SlAm",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "bf91585d-8b22-4bd9-d3ab-111d991cbd9b"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\u001b[?25l     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m0.0/1.3 MB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r\u001b[2K     \u001b[91m━━━━━━\u001b[0m\u001b[91m╸\u001b[0m\u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m0.2/1.3 MB\u001b[0m \u001b[31m6.5 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r\u001b[2K     \u001b[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[91m╸\u001b[0m \u001b[32m1.3/1.3 MB\u001b[0m \u001b[31m21.7 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.3/1.3 MB\u001b[0m \u001b[31m17.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m73.6/73.6 kB\u001b[0m \u001b[31m8.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m90.0/90.0 kB\u001b[0m \u001b[31m10.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m49.4/49.4 kB\u001b[0m \u001b[31m5.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25h"
          ]
        }
      ],
      "source": [
        "!pip install -q -U langchain==0.0.235 openai"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "2. 设置OPENAI API Key"
      ],
      "metadata": {
        "id": "N2-YYQa1s_jQ"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "import os\n",
        "\n",
        "os.environ['OPENAI_API_KEY'] = \"您的有效openai api key\""
      ],
      "metadata": {
        "id": "OSGQIm6FtD8A"
      },
      "execution_count": 2,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 示例代码"
      ],
      "metadata": {
        "id": "i0wXx4wfWIjF"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "1. 内置回调处理器 `StdOutCallbackHandler`"
      ],
      "metadata": {
        "id": "lEPqka5QWcph"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.callbacks import StdOutCallbackHandler\n",
        "from langchain.chains import LLMChain\n",
        "from langchain.llms import OpenAI\n",
        "from langchain.prompts import PromptTemplate\n",
        "\n",
        "handler = StdOutCallbackHandler()\n",
        "llm = OpenAI()\n",
        "prompt = PromptTemplate.from_template(\"Who is {name}?\")\n",
        "chain = LLMChain(llm=llm, prompt=prompt, callbacks=[handler])\n",
        "chain.run(name=\"Super Mario\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 202
        },
        "id": "jhoNdmhQs1J3",
        "outputId": "3e110c89-750d-4f64-c0b9-8bc49bb24895"
      },
      "execution_count": 3,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\n",
            "\n",
            "\u001b[1m> Entering new LLMChain chain...\u001b[0m\n",
            "Prompt after formatting:\n",
            "\u001b[32;1m\u001b[1;3mWho is Super Mario?\u001b[0m\n",
            "\n",
            "\u001b[1m> Finished chain.\u001b[0m\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "'\\n\\nSuper Mario is the protagonist of the popular video game franchise of the same name created by Nintendo. He is a fictional character who stars in video games, television shows, comic books, and films. He is a plumber who is usually portrayed as a portly Italian-American, who is often accompanied by his brother Luigi. He is well known for his catchphrase \"It\\'s-a me, Mario!\"'"
            ],
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            }
          },
          "metadata": {},
          "execution_count": 3
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "2. 自定义回调处理器\n",
        "\n",
        "我们来实现一个处理器,统计每次 `LLM` 交互的处理时间。"
      ],
      "metadata": {
        "id": "NS9I0GYhs4Ny"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.callbacks.base import BaseCallbackHandler\n",
        "import time\n",
        "\n",
        "class TimerHandler(BaseCallbackHandler):\n",
        "\n",
        "    def __init__(self) -> None:\n",
        "        super().__init__()\n",
        "        self.previous_ms = None\n",
        "        self.durations = []\n",
        "\n",
        "    def current_ms(self):\n",
        "        return int(time.time() * 1000 + time.perf_counter() % 1 * 1000)\n",
        "\n",
        "    def on_chain_start(self, serialized, inputs, **kwargs) -> None:\n",
        "        self.previous_ms = self.current_ms()\n",
        "\n",
        "    def on_chain_end(self, outputs, **kwargs) -> None:\n",
        "        if self.previous_ms:\n",
        "          duration = self.current_ms() - self.previous_ms\n",
        "          self.durations.append(duration)\n",
        "\n",
        "    def on_llm_start(self, serialized, prompts, **kwargs) -> None:\n",
        "        self.previous_ms = self.current_ms()\n",
        "\n",
        "    def on_llm_end(self, response, **kwargs) -> None:\n",
        "        if self.previous_ms:\n",
        "          duration = self.current_ms() - self.previous_ms\n",
        "          self.durations.append(duration)"
      ],
      "metadata": {
        "id": "WJQTUyrnwIf6"
      },
      "execution_count": 20,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "llm = OpenAI()\n",
        "timerHandler = TimerHandler()\n",
        "prompt = PromptTemplate.from_template(\"What is the HEX code of color {color_name}?\")\n",
        "chain = LLMChain(llm=llm, prompt=prompt, callbacks=[timerHandler])\n",
        "response = chain.run(color_name=\"blue\")\n",
        "print(response)\n",
        "response = chain.run(color_name=\"purple\")\n",
        "print(response)\n",
        "\n",
        "timerHandler.durations"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "B7Y9E34VPObp",
        "outputId": "5d5f7945-8ad2-43ce-ce52-b6787ae61dac"
      },
      "execution_count": 17,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "{'color_name': 'blue'}\n",
            "{'text': '\\n\\nThe HEX code of color blue is #0000FF.'}\n",
            "\n",
            "\n",
            "The HEX code of color blue is #0000FF.\n",
            "{'color_name': 'purple'}\n",
            "{'text': '\\n\\nThe HEX code of color purple is #800080.'}\n",
            "\n",
            "\n",
            "The HEX code of color purple is #800080.\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[1065, 1075]"
            ]
          },
          "metadata": {},
          "execution_count": 17
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "3. `Model` 与 `callbacks`\n",
        "\n",
        "`callbacks` 可以在构造函数中指定,也可以在执行期间的函数调用中指定。\n",
        "\n",
        "请参考如下代码:"
      ],
      "metadata": {
        "id": "3U6j88miOHis"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "timerHandler = TimerHandler()\n",
        "llm = OpenAI(callbacks=[timerHandler])\n",
        "response = llm.predict(\"What is the HEX code of color BLACK?\")\n",
        "print(response)\n",
        "\n",
        "timerHandler.durations"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Gh8e7GWzOOkT",
        "outputId": "2af5da44-5a0f-40b0-8e55-1c0643c9c4a8"
      },
      "execution_count": 22,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "['What is the HEX code of color BLACK?']\n",
            "generations=[[Generation(text='\\n\\nThe hex code of black is #000000.', generation_info={'finish_reason': 'stop', 'logprobs': None})]] llm_output={'token_usage': {'prompt_tokens': 10, 'total_tokens': 21, 'completion_tokens': 11}, 'model_name': 'text-davinci-003'} run=None\n",
            "\n",
            "\n",
            "The hex code of black is #000000.\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[1223]"
            ]
          },
          "metadata": {},
          "execution_count": 22
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "timerHandler = TimerHandler()\n",
        "llm = OpenAI()\n",
        "response = llm.predict(\"What is the HEX code of color BLACK?\", callbacks=[timerHandler])\n",
        "print(response)\n",
        "\n",
        "timerHandler.durations"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Wm94hadJR1a2",
        "outputId": "00e4053b-0006-40f9-9c1a-d6ae1afef3c6"
      },
      "execution_count": 25,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "['What is the HEX code of color BLACK?']\n",
            "generations=[[Generation(text='\\n\\nThe Hex code of the color black is #000000.', generation_info={'finish_reason': 'stop', 'logprobs': None})]] llm_output={'token_usage': {'prompt_tokens': 10, 'total_tokens': 23, 'completion_tokens': 13}, 'model_name': 'text-davinci-003'} run=None\n",
            "\n",
            "\n",
            "The Hex code of the color black is #000000.\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[1777]"
            ]
          },
          "metadata": {},
          "execution_count": 25
        }
      ]
    }
  ]
}

================================================
FILE: 09_Callbacks/README.md
================================================
---
title: 09. 回调 (Callback)
tags:
  - openai
  - llm
  - langchain
---

# WTF Langchain极简入门: 09. 回调 (Callback)

最近在学习Langchain框架,顺手写一个“WTF Langchain极简入门”,供小白们使用(编程大佬可以另找教程)。本教程默认以下前提:
- 使用Python版本的[Langchain](https://github.com/hwchase17/langchain)
- LLM使用OpenAI的模型
- Langchain目前还处于快速发展阶段,版本迭代频繁,为避免示例代码失效,本教程统一使用版本 **0.0.235**

根据Langchain的[代码约定](https://github.com/hwchase17/langchain/blob/v0.0.235/pyproject.toml#L14C1-L14C24),Python版本 ">=3.8.1,<4.0"。

推特:[@verysmallwoods](https://twitter.com/verysmallwoods)

所有代码和教程开源在github: [github.com/sugarforever/wtf-langchain](https://github.com/sugarforever/wtf-langchain)

-----

## 简介

`Callback` 是 `LangChain` 提供的回调机制,允许我们在 `LLM` 应用程序的各个阶段使用 `Hook`(钩子)。这对于记录日志、监控、流式传输等任务非常有用。这些任务的执行逻辑由回调处理器(`CallbackHandler`)定义。

在 `Python` 程序中, 回调处理器通过继承 `BaseCallbackHandler` 来实现。`BaseCallbackHandler` 接口对每一个可订阅的事件定义了一个回调函数。`BaseCallbackHandler` 的子类可以实现这些回调函数来处理事件。当事件触发时,`LangChain` 的回调管理器 `CallbackManager` 会调用相应的回调函数。

以下是 `BaseCallbackHandler` 的定义。请参考[源代码](https://github.com/langchain-ai/langchain/blob/v0.0.235/langchain/callbacks/base.py#L225)。

```python
class BaseCallbackHandler:
    """Base callback handler that can be used to handle callbacks from langchain."""

    def on_llm_start(
        self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
    ) -> Any:
        """Run when LLM starts running."""

    def on_chat_model_start(
        self, serialized: Dict[str, Any], messages: List[List[BaseMessage]], **kwargs: Any
    ) -> Any:
        """Run when Chat Model starts running."""

    def on_llm_new_token(self, token: str, **kwargs: Any) -> Any:
        """Run on new LLM token. Only available when streaming is enabled."""

    def on_llm_end(self, response: LLMResult, **kwargs: Any) -> Any:
        """Run when LLM ends running."""

    def on_llm_error(
        self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
    ) -> Any:
        """Run when LLM errors."""

    def on_chain_start(
        self, serialized: Dict[str, Any], inputs: Dict[str, Any], **kwargs: Any
    ) -> Any:
        """Run when chain starts running."""

    def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> Any:
        """Run when chain ends running."""

    def on_chain_error(
        self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
    ) -> Any:
        """Run when chain errors."""

    def on_tool_start(
        self, serialized: Dict[str, Any], input_str: str, **kwargs: Any
    ) -> Any:
        """Run when tool starts running."""

    def on_tool_end(self, output: str, **kwargs: Any) -> Any:
        """Run when tool ends running."""

    def on_tool_error(
        self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
    ) -> Any:
        """Run when tool errors."""

    def on_text(self, text: str, **kwargs: Any) -> Any:
        """Run on arbitrary text."""

    def on_agent_action(self, action: AgentAction, **kwargs: Any) -> Any:
        """Run on agent action."""

    def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> Any:
        """Run on agent end."""
```

`LangChain` 内置支持了一系列回调处理器,我们也可以按需求自定义处理器,以实现特定的业务。

## 内置处理器

`StdOutCallbackHandler` 是 `LangChain` 所支持的最基本的处理器。它将所有的回调信息打印到标准输出。这对于调试非常有用。

`LangChain` 链的基类 `Chain` 提供了一个 `callbacks` 参数来指定要使用的回调处理器。请参考[`Chain源码`](https://github.com/langchain-ai/langchain/blob/v0.0.235/langchain/chains/base.py#L63),其中代码片段为:

```python
class Chain(Serializable, ABC):
    """Abstract base class for creating structured sequences of calls to components.
    ...
    callbacks: Callbacks = Field(default=None, exclude=True)
    """Optional list of callback handlers (or callback manager). Defaults to None.
    Callback handlers are called throughout the lifecycle of a call to a chain,
    starting with on_chain_start, ending with on_chain_end or on_chain_error.
    Each custom chain can optionally call additional callback methods, see Callback docs
    for full details."""
```

用法如下:

```python
from langchain.callbacks import StdOutCallbackHandler
from langchain.chains import LLMChain
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate

handler = StdOutCallbackHandler()
llm = OpenAI()
prompt = PromptTemplate.from_template("Who is {name}?")
chain = LLMChain(llm=llm, prompt=prompt, callbacks=[handler])
chain.run(name="Super Mario")
```

你应该期望如下输出:

```shell
> Entering new LLMChain chain...
Prompt after formatting:
Who is Super Mario?

> Finished chain.

\n\nSuper Mario is the protagonist of the popular video game franchise of the same name created by Nintendo. He is a fictional character who stars in video games, television shows, comic books, and films. He is a plumber who is usually portrayed as a portly Italian-American, who is often accompanied by his brother Luigi. He is well known for his catchphrase "It\'s-a me, Mario!"
```

## 自定义处理器

我们可以通过继承 `BaseCallbackHandler` 来实现自定义的回调处理器。下面是一个简单的例子,`TimerHandler` 将跟踪 `Chain` 或 `LLM` 交互的起止时间,并统计每次交互的处理耗时。

```python
from langchain.callbacks.base import BaseCallbackHandler
import time

class TimerHandler(BaseCallbackHandler):

    def __init__(self) -> None:
        super().__init__()
        self.previous_ms = None
        self.durations = []

    def current_ms(self):
        return int(time.time() * 1000 + time.perf_counter() % 1 * 1000)

    def on_chain_start(self, serialized, inputs, **kwargs) -> None:
        self.previous_ms = self.current_ms()

    def on_chain_end(self, outputs, **kwargs) -> None:
        if self.previous_ms:
          duration = self.current_ms() - self.previous_ms
          self.durations.append(duration)

    def on_llm_start(self, serialized, prompts, **kwargs) -> None:
        self.previous_ms = self.current_ms()

    def on_llm_end(self, response, **kwargs) -> None:
        if self.previous_ms:
          duration = self.current_ms() - self.previous_ms
          self.durations.append(duration)

llm = OpenAI()
timerHandler = TimerHandler()
prompt = PromptTemplate.from_template("What is the HEX code of color {color_name}?")
chain = LLMChain(llm=llm, prompt=prompt, callbacks=[timerHandler])
response = chain.run(color_name="blue")
print(response)
response = chain.run(color_name="purple")
print(response)

timerHandler.durations
```

你应该期望如下输出:

```shell
The HEX code for blue is #0000FF.
The HEX code of the color purple is #800080.
[1589, 1097]
```

## 回调处理器的适用场景

通过 `LLMChain` 的构造函数参数设置 `callbacks` 仅仅是众多适用场景之一。接下来我们简明地列出其他使用场景和示例代码。

对于 `Model`,`Agent`, `Tool`,以及 `Chain` 都可以通过以下方式设置回调处理器:
### 1. 构造函数参数 `callbacks` 设置

关于 `Chain`,以 `LLMChain` 为例,请参考本讲上一部分内容。注意在 `Chain` 上的回调器监听的是 `chain` 相关的事件,因此回调器的如下函数会被调用:
- on_chain_start
- on_chain_end
- on_chain_error

`Agent`, `Tool`,以及 `Chain` 上的回调器会分别被调用相应的回调函数。

下面分享关于 `Model` 与 `callbacks` 的使用示例:

```python
timerHandler = TimerHandler()
llm = OpenAI(callbacks=[timerHandler])
response = llm.predict("What is the HEX code of color BLACK?")
print(response)

timerHandler.durations
```

你应该期望看到类似如下的输出:

```shell
['What is the HEX code of color BLACK?']
generations=[[Generation(text='\n\nThe hex code of black is #000000.', generation_info={'finish_reason': 'stop', 'logprobs': None})]] llm_output={'token_usage': {'prompt_tokens': 10, 'total_tokens': 21, 'completion_tokens': 11}, 'model_name': 'text-davinci-003'} run=None


The hex code of black is #000000.

[1223]
```

### 2. 通过运行时的函数调用

`Model`,`Agent`, `Tool`,以及 `Chain` 的请求执行函数都接受 `callbacks` 参数,比如 `LLMChain` 的 `run` 函数,`OpenAI` 的 `predict` 函数,等都能接受 `callbacks` 参数,在运行时指定回调处理器。

以 `OpenAI` 模型类为例:

```python
timerHandler = TimerHandler()
llm = OpenAI()
response = llm.predict("What is the HEX code of color BLACK?", callbacks=[timerHandler])
print(response)

timerHandler.durations
```

你应该同样期望如下输出:

```shell
['What is the HEX code of color BLACK?']
generations=[[Generation(text='\n\nThe hex code of black is #000000.', generation_info={'finish_reason': 'stop', 'logprobs': None})]] llm_output={'token_usage': {'prompt_tokens': 10, 'total_tokens': 21, 'completion_tokens': 11}, 'model_name': 'text-davinci-003'} run=None

The hex code of black is #000000.

[1138]
```

关于 `Agent`,`Tool` 等的使用,请参考官方文档API。

## 总结

本节课程中,我们学习了什么是 `Callback` 回调,如何使用回调处理器,以及在哪些场景下可以接入回调处理器。下一讲,我们将一起完成一个完整的应用案例,来巩固本系列课程的知识点。

本节课程的完整示例代码,请参考 [09_Callbacks.ipynb](./09_Callbacks.ipynb)。

### 相关文档资料链接:
1. [Python Langchain官方文档](https://python.langchain.com/docs/get_started/introduction.html) 

================================================
FILE: 10_Example/10_Example.ipynb
================================================
{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": [],
      "authorship_tag": "ABX9TyNqlLD/LEX3MZ6Uw13WCE8x",
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/sugarforever/wtf-langchain/blob/main/10_Example/10_Example.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# 一个完整的例子\n",
        "\n",
        "这是该 `LangChain` 极简入门系列的最后一讲。我们将利用过去9讲学习的知识,来完成一个具备完整功能集的LLM应用。该应用基于 `LangChain` 框架,以某 `PDF` 文件的内容为知识库,提供给用户基于该文件内容的问答能力。\n",
        "\n",
        "我们利用 `LangChain` 的QA chain,结合 `Chroma` 来实现PDF文档的语义化搜索。示例代码所引用的是[AWS Serverless\n",
        "Developer Guide](https://docs.aws.amazon.com/pdfs/serverless/latest/devguide/serverless-core.pdf),该PDF文档共84页。"
      ],
      "metadata": {
        "id": "69PRFT6WO-oK"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "1. 安装必要的 `Python` 包"
      ],
      "metadata": {
        "id": "OBehQYkOPPWe"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "!pip install -q langchain==0.0.235 openai chromadb pymupdf tiktoken"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "_amYPxT-PULc",
        "outputId": "d3d7515d-b214-4140-b39b-a9a209cf00b9"
      },
      "execution_count": 1,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\u001b[?25l     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m0.0/1.3 MB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r\u001b[2K     \u001b[91m━━━━━━\u001b[0m\u001b[91m╸\u001b[0m\u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m0.2/1.3 MB\u001b[0m \u001b[31m6.5 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r\u001b[2K     \u001b[91m━━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[90m╺\u001b[0m\u001b[90m━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m0.7/1.3 MB\u001b[0m \u001b[31m10.1 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r\u001b[2K     \u001b[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[91m╸\u001b[0m\u001b[90m━━\u001b[0m \u001b[32m1.2/1.3 MB\u001b[0m \u001b[31m11.9 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.3/1.3 MB\u001b[0m \u001b[31m10.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m73.6/73.6 kB\u001b[0m \u001b[31m8.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m405.5/405.5 kB\u001b[0m \u001b[31m13.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m14.1/14.1 MB\u001b[0m \u001b[31m44.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.7/1.7 MB\u001b[0m \u001b[31m57.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m90.0/90.0 kB\u001b[0m \u001b[31m12.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m3.1/3.1 MB\u001b[0m \u001b[31m59.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25h  Installing build dependencies ... \u001b[?25l\u001b[?25hdone\n",
            "  Getting requirements to build wheel ... \u001b[?25l\u001b[?25hdone\n",
            "  Preparing metadata (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m58.4/58.4 kB\u001b[0m \u001b[31m6.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m59.5/59.5 kB\u001b[0m \u001b[31m7.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m5.3/5.3 MB\u001b[0m \u001b[31m74.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m5.9/5.9 MB\u001b[0m \u001b[31m84.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m7.8/7.8 MB\u001b[0m \u001b[31m103.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m67.3/67.3 kB\u001b[0m \u001b[31m8.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25h  Installing build dependencies ... \u001b[?25l\u001b[?25hdone\n",
            "  Getting requirements to build wheel ... \u001b[?25l\u001b[?25hdone\n",
            "  Preparing metadata (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m49.4/49.4 kB\u001b[0m \u001b[31m5.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m67.0/67.0 kB\u001b[0m \u001b[31m7.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m46.0/46.0 kB\u001b[0m \u001b[31m5.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m58.3/58.3 kB\u001b[0m \u001b[31m7.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m428.8/428.8 kB\u001b[0m \u001b[31m36.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m4.1/4.1 MB\u001b[0m \u001b[31m116.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.3/1.3 MB\u001b[0m \u001b[31m78.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m129.9/129.9 kB\u001b[0m \u001b[31m15.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m86.8/86.8 kB\u001b[0m \u001b[31m11.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25h  Building wheel for chroma-hnswlib (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n",
            "  Building wheel for pypika (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "2. 设置OpenAI环境"
      ],
      "metadata": {
        "id": "8Hihrnw_PeIA"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "import os\n",
        "os.environ['OPENAI_API_KEY'] = ''"
      ],
      "metadata": {
        "id": "dALQoneUPgEH"
      },
      "execution_count": 2,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "3. 下载PDF文件AWS Serverless Developer Guide"
      ],
      "metadata": {
        "id": "8aB0OBRFP5FC"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "!wget https://docs.aws.amazon.com/pdfs/serverless/latest/devguide/serverless-core.pdf\n",
        "\n",
        "PDF_NAME = 'serverless-core.pdf'"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "zF-PFO9BP6wr",
        "outputId": "1d761def-1df0-4043-f00e-be6dd913f1b2"
      },
      "execution_count": 3,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "--2023-08-17 11:42:20--  https://docs.aws.amazon.com/pdfs/serverless/latest/devguide/serverless-core.pdf\n",
            "Resolving docs.aws.amazon.com (docs.aws.amazon.com)... 108.159.227.88, 108.159.227.51, 108.159.227.3, ...\n",
            "Connecting to docs.aws.amazon.com (docs.aws.amazon.com)|108.159.227.88|:443... connected.\n",
            "HTTP request sent, awaiting response... 200 OK\n",
            "Length: 4727395 (4.5M) [application/pdf]\n",
            "Saving to: ‘serverless-core.pdf’\n",
            "\n",
            "serverless-core.pdf 100%[===================>]   4.51M  12.0MB/s    in 0.4s    \n",
            "\n",
            "2023-08-17 11:42:21 (12.0 MB/s) - ‘serverless-core.pdf’ saved [4727395/4727395]\n",
            "\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "4. 加载PDF文件"
      ],
      "metadata": {
        "id": "WqBDCt0HQFAA"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.document_loaders import PyMuPDFLoader\n",
        "docs = PyMuPDFLoader(PDF_NAME).load()\n",
        "\n",
        "print (f'There are {len(docs)} document(s) in {PDF_NAME}.')\n",
        "print (f'There are {len(docs[0].page_content)} characters in the first page of your document.')"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "bniPzdhUQSlw",
        "outputId": "01342468-bef4-4e9f-9b56-1ab6eac65ab4"
      },
      "execution_count": 4,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "There are 84 document(s) in serverless-core.pdf.\n",
            "There are 27 characters in the first page of your document.\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "5. 拆分文档并存储文本嵌入的向量数据"
      ],
      "metadata": {
        "id": "V9kvXY9uQ1mI"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.embeddings.openai import OpenAIEmbeddings\n",
        "from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
        "from langchain.vectorstores import Chroma\n",
        "\n",
        "text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)\n",
        "split_docs = text_splitter.split_documents(docs)\n",
        "\n",
        "embeddings = OpenAIEmbeddings()\n",
        "\n",
        "vectorstore = Chroma.from_documents(split_docs, embeddings, collection_name=\"serverless_guide\")"
      ],
      "metadata": {
        "id": "G4d8cwQTQ2fa"
      },
      "execution_count": 5,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "6. 基于OpenAI创建QA链"
      ],
      "metadata": {
        "id": "-T6_mIR8RwEF"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain.llms import OpenAI\n",
        "from langchain.chains.question_answering import load_qa_chain\n",
        "\n",
        "llm = OpenAI(temperature=0)\n",
        "chain = load_qa_chain(llm, chain_type=\"stuff\")"
      ],
      "metadata": {
        "id": "BsW99LnUR2Ns"
      },
      "execution_count": 6,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "7. 基于提问,进行相似性查询"
      ],
      "metadata": {
        "id": "ED54hPgfSXYL"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "query = \"What is the use case of AWS Serverless?\"\n",
        "similar_docs = vectorstore.similarity_search(query, 3, include_metadata=True)"
      ],
      "metadata": {
        "id": "bPmKM4zXSam9"
      },
      "execution_count": 7,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "similar_docs"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "DNog2ekVSxPa",
        "outputId": "78bf8f0c-bdf2-4b9c-8a08-433c7706b758"
      },
      "execution_count": 8,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[Document(page_content='Serverless\\nDeveloper Guide', metadata={'author': 'AWS', 'creationDate': 'D:20230817052259Z', 'creator': 'ZonBook XSL Stylesheets with Apache FOP', 'file_path': 'serverless-core.pdf', 'format': 'PDF 1.4', 'keywords': 'Serverless, serverless guide, getting started serverless, event-driven architecture, Lambda, API Gateway, DynamoDB, serverless, developer, guide, learn serverless, serverless, use-case, serverless, prerequisites, serverless, serverless, fundamentals, even-driven, architecture, serverless, fundamentals, serverless, developer_experience, lifecycle, deploy, packaging, serverless, hands-on, tutorial, workshop, next steps, security, serverless, compute, api, gateway, serverless, database, nosql', 'modDate': '', 'page': 0, 'producer': 'Apache FOP Version 2.6', 'source': 'serverless-core.pdf', 'subject': '', 'title': 'Serverless - Developer Guide', 'total_pages': 84, 'trapped': ''}),\n",
              " Document(page_content='needed to build serverless solutions.\\nIn serverless solutions, you focus on writing code that serves your customers, without managing servers. \\nServerless technologies are pay-as-you-go, can scale both up and down, and are easy to expand across \\ngeographic regions.\\nMeeting you where you are now\\nWe expect that you are a developer with some traditional web application development experience, \\nbut you are new to Amazon Web Services or serverless architectures. We also assume you want to get \\n1', metadata={'author': 'AWS', 'creationDate': 'D:20230817052259Z', 'creator': 'ZonBook XSL Stylesheets with Apache FOP', 'file_path': 'serverless-core.pdf', 'format': 'PDF 1.4', 'keywords': 'Serverless, serverless guide, getting started serverless, event-driven architecture, Lambda, API Gateway, DynamoDB, serverless, developer, guide, learn serverless, serverless, use-case, serverless, prerequisites, serverless, serverless, fundamentals, even-driven, architecture, serverless, fundamentals, serverless, developer_experience, lifecycle, deploy, packaging, serverless, hands-on, tutorial, workshop, next steps, security, serverless, compute, api, gateway, serverless, database, nosql', 'modDate': '', 'page': 4, 'producer': 'Apache FOP Version 2.6', 'source': 'serverless-core.pdf', 'subject': '', 'title': 'Serverless - Developer Guide', 'total_pages': 84, 'trapped': ''}),\n",
              " Document(page_content='infrastructure (regions, ARNs, security model). Then, it will introduce the shift in mindset you need to \\nmake to start your serverless journey. Next, it will dive into event-driven architecture and other serverless \\nconcepts necessary to transition to serverless.\\nAlong the way, the guide will provide a list of curated resources, such as articles, workshops, and \\ntutorials, to reinforce your learning with hands-on activities.\\nThe guide will focus on common serverless use cases, such as:\\n• Interactive Web- and API-based microservices or applications\\n• Data processing applications\\n• Real-time streaming applications\\n• Machine learning\\n• IT automation and service orchestration\\nTo avoid information overload, scope will be limited to a few essential serverless services to get started:\\n• AWS Identity and Access Management for service security\\n• AWS Lambda for compute functions\\n• Amazon API Gateway for integrating HTTP/S requests with other services that handle the requests', metadata={'author': 'AWS', 'creationDate': 'D:20230817052259Z', 'creator': 'ZonBook XSL Stylesheets with Apache FOP', 'file_path': 'serverless-core.pdf', 'format': 'PDF 1.4', 'keywords': 'Serverless, serverless guide, getting started serverless, event-driven architecture, Lambda, API Gateway, DynamoDB, serverless, developer, guide, learn serverless, serverless, use-case, serverless, prerequisites, serverless, serverless, fundamentals, even-driven, architecture, serverless, fundamentals, serverless, developer_experience, lifecycle, deploy, packaging, serverless, hands-on, tutorial, workshop, next steps, security, serverless, compute, api, gateway, serverless, database, nosql', 'modDate': '', 'page': 5, 'producer': 'Apache FOP Version 2.6', 'source': 'serverless-core.pdf', 'subject': '', 'title': 'Serverless - Developer Guide', 'total_pages': 84, 'trapped': ''})]"
            ]
          },
          "metadata": {},
          "execution_count": 8
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "8. 基于相关文档,利用QA链完成回答"
      ],
      "metadata": {
        "id": "1XecjykTSnve"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "chain.run(input_documents=similar_docs, question=query)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 54
        },
        "id": "E4YOeM8aSuEY",
        "outputId": "14ccdea1-f586-45cf-bf4c-cce99322739c"
      },
      "execution_count": 9,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "' AWS Serverless can be used for interactive web- and API-based microservices or applications, data processing applications, real-time streaming applications, machine learning, and IT automation and service orchestration.'"
            ],
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            }
          },
          "metadata": {},
          "execution_count": 9
        }
      ]
    }
  ]
}

================================================
FILE: 10_Example/README.md
================================================
---
title: 10. 一个完整的例子
tags:
  - openai
  - llm
  - langchain
---

# WTF Langchain极简入门: 10. 一个完整的例子

最近在学习Langchain框架,顺手写一个“WTF Langchain极简入门”,供小白们使用(编程大佬可以另找教程)。本教程默认以下前提:
- 使用Python版本的[Langchain](https://github.com/hwchase17/langchain)
- LLM使用OpenAI的模型
- Langchain目前还处于快速发展阶段,版本迭代频繁,为避免示例代码失效,本教程统一使用版本 **0.0.235**

根据Langchain的[代码约定](https://github.com/hwchase17/langchain/blob/v0.0.235/pyproject.toml#L14C1-L14C24),Python版本 ">=3.8.1,<4.0"。

推特:[@verysmallwoods](https://twitter.com/verysmallwoods)

所有代码和教程开源在github: [github.com/sugarforever/wtf-langchain](https://github.com/sugarforever/wtf-langchain)

-----

## 简介

这是该 `LangChain` 极简入门系列的最后一讲。我们将利用过去9讲学习的知识,来完成一个具备完整功能集的LLM应用。该应用基于 `LangChain` 框架,以某 `PDF` 文件的内容为知识库,提供给用户基于该文件内容的问答能力。

我们利用 `LangChain` 的QA chain,结合 `Chroma` 来实现PDF文档的语义化搜索。示例代码所引用的是[AWS Serverless
Developer Guide](https://docs.aws.amazon.com/pdfs/serverless/latest/devguide/serverless-core.pdf),该PDF文档共84页。

本讲的完整代码请参考[10_Example.jpynb](./10_Example.ipynb)

1. 安装必要的 `Python` 包

    ```shell
    !pip install -q langchain==0.0.235 openai chromadb pymupdf tiktoken
    ```

2. 设置OpenAI环境

    ```python
    import os
    os.environ['OPENAI_API_KEY'] = '您的有效openai api key'
    ```

3. 下载PDF文件AWS Serverless Developer Guide

    ```python
    !wget https://docs.aws.amazon.com/pdfs/serverless/latest/devguide/serverless-core.pdf

    PDF_NAME = 'serverless-core.pdf'
    ```

4. 加载PDF文件

    ```python
    from langchain.document_loaders import PyMuPDFLoader
    docs = PyMuPDFLoader(PDF_NAME).load()

    print (f'There are {len(docs)} document(s) in {PDF_NAME}.')
    print (f'There are {len(docs[0].page_content)} characters in the first page of your document.')
    ```

5. 拆分文档并存储文本嵌入的向量数据

    ```python
    from langchain.embeddings.openai import OpenAIEmbeddings
    from langchain.text_splitter import RecursiveCharacterTextSplitter
    from langchain.vectorstores import Chroma

    text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
    split_docs = text_splitter.split_documents(docs)

    embeddings = OpenAIEmbeddings()

    vectorstore = Chroma.from_documents(split_docs, embeddings, collection_name="serverless_guide")
    ```

6. 基于OpenAI创建QA链

    ```python
    from langchain.llms import OpenAI
    from langchain.chains.question_answering import load_qa_chain

    llm = OpenAI(temperature=0)
    chain = load_qa_chain(llm, chain_type="stuff")
    ```

7. 基于提问,进行相似性查询
    
    ```python
    query = "What is the use case of AWS Serverless?"
    similar_docs = vectorstore.similarity_search(query, 3, include_metadata=True)
    ```

8. 基于相关文档,利用QA链完成回答

    ```python
    chain.run(input_documents=similar_docs, question=query)
    ```

## 总结

本节课程中,我们利用所学的知识,完成了第一个完整的LLM应用。希望通过本系列的学习,大家能对 `LangChain` 框架的使用,有了基本的认识,并且掌握了框架核心组建的使用方法。

### 相关文档资料链接:
1. [Python Langchain官方文档](https://python.langchain.com/docs/get_started/introduction.html) 

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 sugarforever

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# WTF Langchain

最近在学习Langchain框架,顺手写一个开源教程。本教程默认以下前提:
- 使用Python版本的[Langchain](https://github.com/hwchase17/langchain)
- LLM使用OpenAI的模型
- Langchain目前还处于快速发展阶段,版本迭代频繁,为避免示例代码失效,本教程使用最新版本 **0.0.235**
## 极简入门

**01: Hello Langchain**:[链接](./01_Hello_Langchain)

**02: 模型**:[链接](./02_Models)

**03: 数据连接**:[链接](./03_Data_Connections)

**04: 提示词**:[链接](./04_Prompts)

**05: 输出解析器**:[链接](./05_Output_Parsers)

**06: 链**:[链接](./06_Chains)

**07: 记忆组件**:[链接](./07_Memory)

**08: 代理**:[链接](./08_Agents)

**09: 回调**:[链接](./09_Callbacks)

**10: 一个完整的例子**:[链接](./10_Example)
Download .txt
gitextract_ok6e_leo/

├── 01_Hello_Langchain/
│   ├── Hello_Langchain.ipynb
│   └── README.md
├── 02_Models/
│   ├── Models.ipynb
│   └── README.md
├── 03_Data_Connections/
│   ├── 03_Data_Connections.ipynb
│   └── README.md
├── 04_Prompts/
│   ├── 04_Prompts.ipynb
│   └── README.md
├── 05_Output_Parsers/
│   ├── 05_Output_Parsers.ipynb
│   └── README.md
├── 06_Chains/
│   ├── 06_Chains.ipynb
│   └── README.md
├── 07_Memory/
│   ├── 07_Memory.ipynb
│   └── README.md
├── 08_Agents/
│   ├── 08_Agents.ipynb
│   └── README.md
├── 09_Callbacks/
│   ├── 09_Callbacks.ipynb
│   └── README.md
├── 10_Example/
│   ├── 10_Example.ipynb
│   └── README.md
├── LICENSE
└── README.md
Condensed preview — 22 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (195K chars).
[
  {
    "path": "01_Hello_Langchain/Hello_Langchain.ipynb",
    "chars": 1857,
    "preview": "{\n  \"cells\": [\n    {\n      \"cell_type\": \"markdown\",\n      \"metadata\": {\n        \"id\": \"view-in-github\",\n        \"colab_t"
  },
  {
    "path": "01_Hello_Langchain/README.md",
    "chars": 3324,
    "preview": "---\ntitle: 01. Hello Langchain\ntags:\n  - openai\n  - llm\n  - langchain\n---\n\n# WTF Langchain极简入门: 01. Hello Langchain\n\n最近在"
  },
  {
    "path": "02_Models/Models.ipynb",
    "chars": 5234,
    "preview": "{\n  \"cells\": [\n    {\n      \"cell_type\": \"markdown\",\n      \"metadata\": {\n        \"colab_type\": \"text\",\n        \"id\": \"vie"
  },
  {
    "path": "02_Models/README.md",
    "chars": 4890,
    "preview": "---\ntitle: 02. 模型\ntags:\n  - openai\n  - llm\n  - langchain\n---\n\n# WTF Langchain极简入门: 02. 模型\n\n最近在学习Langchain框架,顺手写一个“WTF La"
  },
  {
    "path": "03_Data_Connections/03_Data_Connections.ipynb",
    "chars": 9047,
    "preview": "{\n  \"nbformat\": 4,\n  \"nbformat_minor\": 0,\n  \"metadata\": {\n    \"colab\": {\n      \"provenance\": [],\n      \"authorship_tag\":"
  },
  {
    "path": "03_Data_Connections/README.md",
    "chars": 6270,
    "preview": "---\ntitle: 03. 数据连接\ntags:\n  - openai\n  - llm\n  - langchain\n---\n\n# WTF Langchain极简入门: 03. 数据连接\n\n最近在学习Langchain框架,顺手写一个“WT"
  },
  {
    "path": "04_Prompts/04_Prompts.ipynb",
    "chars": 9795,
    "preview": "{\n  \"nbformat\": 4,\n  \"nbformat_minor\": 0,\n  \"metadata\": {\n    \"colab\": {\n      \"provenance\": [],\n      \"authorship_tag\":"
  },
  {
    "path": "04_Prompts/README.md",
    "chars": 5289,
    "preview": "---\ntitle: 04. 提示词\ntags:\n  - openai\n  - llm\n  - langchain\n---\n\n# WTF Langchain极简入门: 04. 提示词\n\n最近在学习Langchain框架,顺手写一个“WTF "
  },
  {
    "path": "05_Output_Parsers/05_Output_Parsers.ipynb",
    "chars": 7501,
    "preview": "{\n  \"nbformat\": 4,\n  \"nbformat_minor\": 0,\n  \"metadata\": {\n    \"colab\": {\n      \"provenance\": [],\n      \"authorship_tag\":"
  },
  {
    "path": "05_Output_Parsers/README.md",
    "chars": 3758,
    "preview": "---\ntitle: 05. 输出解析器\ntags:\n  - openai\n  - llm\n  - langchain\n---\n\n# WTF Langchain极简入门: 05. 输出解析器\n\n最近在学习Langchain框架,顺手写一个“"
  },
  {
    "path": "06_Chains/06_Chains.ipynb",
    "chars": 6118,
    "preview": "{\n  \"nbformat\": 4,\n  \"nbformat_minor\": 0,\n  \"metadata\": {\n    \"colab\": {\n      \"provenance\": [],\n      \"authorship_tag\":"
  },
  {
    "path": "06_Chains/README.md",
    "chars": 4281,
    "preview": "---\ntitle: 06. 链\ntags:\n  - openai\n  - llm\n  - langchain\n---\n\n# WTF Langchain极简入门: 06. 链\n\n最近在学习Langchain框架,顺手写一个“WTF Lang"
  },
  {
    "path": "07_Memory/07_Memory.ipynb",
    "chars": 13259,
    "preview": "{\n  \"nbformat\": 4,\n  \"nbformat_minor\": 0,\n  \"metadata\": {\n    \"colab\": {\n      \"provenance\": [],\n      \"authorship_tag\":"
  },
  {
    "path": "07_Memory/README.md",
    "chars": 5009,
    "preview": "---\ntitle: 07. 记忆组件\ntags:\n  - openai\n  - llm\n  - langchain\n---\n\n# WTF Langchain极简入门: 07. 记忆组件\n\n最近在学习Langchain框架,顺手写一个“WT"
  },
  {
    "path": "08_Agents/08_Agents.ipynb",
    "chars": 15158,
    "preview": "{\n  \"nbformat\": 4,\n  \"nbformat_minor\": 0,\n  \"metadata\": {\n    \"colab\": {\n      \"provenance\": [],\n      \"authorship_tag\":"
  },
  {
    "path": "08_Agents/README.md",
    "chars": 10345,
    "preview": "---\ntitle: 08. 代理 (Agent)\ntags:\n  - openai\n  - llm\n  - langchain\n---\n\n# WTF Langchain极简入门: 08. 代理 (Agent)\n\n最近在学习Langchai"
  },
  {
    "path": "09_Callbacks/09_Callbacks.ipynb",
    "chars": 11914,
    "preview": "{\n  \"nbformat\": 4,\n  \"nbformat_minor\": 0,\n  \"metadata\": {\n    \"colab\": {\n      \"provenance\": [],\n      \"authorship_tag\":"
  },
  {
    "path": "09_Callbacks/README.md",
    "chars": 8418,
    "preview": "---\ntitle: 09. 回调 (Callback)\ntags:\n  - openai\n  - llm\n  - langchain\n---\n\n# WTF Langchain极简入门: 09. 回调 (Callback)\n\n最近在学习La"
  },
  {
    "path": "10_Example/10_Example.ipynb",
    "chars": 18302,
    "preview": "{\n  \"nbformat\": 4,\n  \"nbformat_minor\": 0,\n  \"metadata\": {\n    \"colab\": {\n      \"provenance\": [],\n      \"authorship_tag\":"
  },
  {
    "path": "10_Example/README.md",
    "chars": 2918,
    "preview": "---\ntitle: 10. 一个完整的例子\ntags:\n  - openai\n  - llm\n  - langchain\n---\n\n# WTF Langchain极简入门: 10. 一个完整的例子\n\n最近在学习Langchain框架,顺手"
  },
  {
    "path": "LICENSE",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) 2023 sugarforever\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
  },
  {
    "path": "README.md",
    "chars": 564,
    "preview": "# WTF Langchain\n\n最近在学习Langchain框架,顺手写一个开源教程。本教程默认以下前提:\n- 使用Python版本的[Langchain](https://github.com/hwchase17/langchain)\n"
  }
]

About this extraction

This page contains the full source code of the sugarforever/wtf-langchain GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 22 files (150.7 KB), approximately 50.2k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!