鹤啸九天 自律更自由,平凡不平庸 Less is More

LangChain 学习笔记

2023-05-24
阅读量

Notes(温馨提示):

  1. ★ 首次阅读建议浏览:导航指南
  2. 右上角工具条搜索文章,右下角二维码关注微信公众号(鹤啸九天),底栏分享、赞赏、评论
  3. ★ 转载请注明文章来源,知识点积累起来不容易,水滴石穿,绳锯木断,谢谢理解
  4. ★ 如有疑问,邮件讨论,欢迎贡献优质资料


LangChain

LangChain 介绍

LangChain, 语言链条,也称:兰链,Harrison Chase 2022年10月创建的一个 Python 库,一种LLM语言大模型开发工具

  • LangChain多语言实现:Python、node.js以及第三方提供的Go
  • 几分钟内构建 GPT 驱动的应用程序。

Harrison Chase 于 2022 年 10 月底首次提交 LangChain。在被卷入 LLM 浪潮之前,只有短短几个月的开发时间

LangChain 可以帮助开发者将LLM与其他计算或知识源结合起来,创建更强大的应用程序。

  • img

论文《ReAct: Synergizing Reasoning and Acting in Language Models》的实现:

  • 该论文展示了一种提示技术,允许模型「推理」(通过思维链)和「行动」(通过能够使用预定义工具集中的工具,例如能够搜索互联网)。

LangChain 在没有任何收入/创收计划的情况下,获得了 1000 万美元的种子轮融资和 2000-2500 万美元的 A 轮融资,估值达到 2 亿美元左右。

功能

LangChain 构建的有趣应用程序包括(但不限于):

  • 聊天机器人
  • 特定领域的总结和问答
  • 查询数据库以获取信息然后处理它们的应用程序
  • 解决特定问题的代理,例如数学和推理难题

垂直领域知识库问答架构变化

架构图 BERT时代 LLM时代
图解
分析 query理解异常重要 LLMs直接把模块的工作包揽了下来,原本的query分词、分类、纠错、关键词等工作变成了制定合适的Prompt

文档介绍

LangChain 安装

安装步骤

  • python 3.8 以上才能安装
pip install langchain
pip install openai
# 环境变量
# ① 在终端中设置环境变量:
export OPENAI_API_KEY = "..."
# ② Jupyter notebook 或 Python 脚本中工作,这样设置环境变量:
import os 
os.environ[ "OPENAI_API_KEY" ] = "..."

测试:构建LLM应用

  • LangChain 目前支持 AIMessage、HumanMessage、SystemMessage 和 ChatMessage 类型。
  • 一般主要使用 HumanMessage、AIMessage 和 SystemMessage。
from langchain.llms import OpenAI
# -------【构建LLM应用】--------
llm = OpenAI(temperature=0.9) # 初始化包装器,temperature越高结果越随机
# 调用
text = "What would be a good company name for a company that makes colorful socks?"
print(llm(text)) # 生成结果,结果是随机的 例如: Glee Socks. Rainbow Cozy SocksKaleidoscope Socks.
# -------【构建Prompt】-------
from langchain.prompts import PromptTemplate

prompt = PromptTemplate(
    input_variables=["product"],
    template="What is a good name for a company that makes {product}?",
)
print(prompt.format(product="colorful socks"))
# 输出结果 What is a good name for a company that makes colorful socks?
# -------【构建聊天应用】-------
from langchain.chat_models import ChatOpenAI
from langchain.schema import (
    AIMessage,
    HumanMessage,
    SystemMessage
)
# ------ 单条消息 --------
chat = ChatOpenAI(temperature=0)
chat([HumanMessage(content="Translate this sentence from English to French. I love programming.")])
#输出结果 AIMessage(content="J'aime programmer.", additional_kwargs={})
# ------ 多条消息:批处理 --------
batch_messages = [
    [
        SystemMessage(content="You are a helpful assistant that translates English to Chinese."),
        HumanMessage(content="Translate this sentence from English to Chinese. I love programming.")
    ],
    [
        SystemMessage(content="You are a helpful assistant that translates English to Chinese."),
        HumanMessage(content="Translate this sentence from English to Chinese. I love artificial intelligence.")
    ],
]
result = chat.generate(batch_messages)
print(result)
result.llm_output['token_usage']
# ------ 消息模板 ------
from langchain.chat_models import ChatOpenAI
# 加模板后,导入方式变化:增加 PromptTemplate后缀
from langchain.prompts.chat import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)
chat = ChatOpenAI(temperature=0)
template="You are a helpful assistant that translates {input_language} to {output_language}."
system_message_prompt = SystemMessagePromptTemplate.from_template(template)
human_template = "{text}"
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)
chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])
# get a chat completion from the formatted messages
chat(chat_prompt.format_prompt(input_language="English", output_language="Chinese", text="I love programming.").to_messages())
# -> AIMessage(content="我喜欢编程。(Wǒ xǐhuān biānchéng.)", additional_kwargs={})

内存与聊天模型初始化的代理一起使用。

  • 与 Memory for LLMs 的主要区别:将以前的消息保留为唯一的内存对象,而不是将压缩成一个字符串。
from langchain.prompts import (
    ChatPromptTemplate, 
    MessagesPlaceholder, # 消息占位符
    SystemMessagePromptTemplate, 
    HumanMessagePromptTemplate
)
from langchain.chains import ConversationChain
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationBufferMemory

prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template("The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know."),
    MessagesPlaceholder(variable_name="history"),
    HumanMessagePromptTemplate.from_template("{input}")
])
llm = ChatOpenAI(temperature=0)
memory = ConversationBufferMemory(return_messages=True)
conversation = ConversationChain(memory=memory, prompt=prompt, llm=llm)
conversation.predict(input="Hi there!") # -> 'Hello! How can I assist you today?'
conversation.predict(input="I'm doing well! Just having a conversation with an AI.") # -> "That sounds like fun! I'm happy to chat with you. Is there anything specific you'd like to talk about?"
conversation.predict(input="Tell me about yourself.")

LangChain 生态

LangSmith 是LangChain官方推出的 生产级LLM应用程序构建平台。

  • 可以调试、测试、评估和监控任何LLM框架上构建的链和智能代理,并与 LangChain 无缝集成,LangChain 是构建LLM的首选开源框架。
  • langchain 执行过程中的数据可视化展示,用于观察 chain tool llm 之前的嵌套关系、执行耗时、token 消耗等指标
  • 官方文档

LangSmith由LangChain开发,LangChain是开源LangChain框架背后的公司。

LangFlow 可视化

【2023-7-4】LangFlowLangChain 的一种图形用户界面(GUI),它为大型语言模型(LLM)提供了易用的实验和原型设计工具。通过使用 LangFlow,用户可以利用 react-flow 轻松构建LLM应用。

LogSpace 出品,LangFlow github 的主要功能包括:

  • 提供多种 LangChain 组件以供选择,如语言模型、提示序列化器、代理和链等。
  • 通过编辑提示参数、链接链和代理,以及跟踪代理的思考过程,用户可以探索这些组件的功能。
  • 使用 LangFlow,用户可以将流程导出为 JSON 文件,然后在 LangChain 中使用。
  • LangFlow 的图形用户界面(GUI)提供了一种直观的方式来进行流程实验和原型开发,包括拖放组件和聊天框
  • 官方 UI 配置文件集合
pip install langflow # 安装
langflow # 启动
python -m langflow # 上面命令不管用时用这个

自动弹出本地web页面: http://127.0.0.1:7860/, 配置可导出为json格式

json格式导入 flow

from langflow import load_flow_from_json

flow = load_flow_from_json("path/to/flow.json")
# Now you can use it like any chain
flow("Hey, have you heard of LangFlow?")

LangSmith

LangSmith 是LangChain官方推出的 生产级LLM应用程序构建平台。

  • 可以调试、测试、评估和监控任何LLM框架上构建的链和智能代理,并与 LangChain 无缝集成,LangChain 是构建LLM的首选开源框架。
  • langchain 执行过程中的数据可视化展示,用于观察 chain tool llm 之前的嵌套关系、执行耗时、token 消耗等指标
  • 官方文档

LangSmith由LangChain开发,LangChain是开源LangChain框架背后的公司。

LangChain 观点

【2023-7-23】我为什么放弃了 LangChain?

由 LangChain 推广的 ReAct 工作流在 InstructGPT/text-davinci-003 中特别有效,但成本很高,而且对于小型项目来说并不容易使用。

「LangChain 是 RAG 最受欢迎的工具,阅读 LangChain 的全面文档,以便更好地理解如何最好地利用它。」

经过一周的研究,运行 LangChain 的 demo 示例可以工作,但是任何调整以适应食谱聊天机器人约束的尝试都会失败。

  • 解决了这些 bug 之后,聊天对话的整体质量很差,而且毫无趣味。经过紧张的调试之后,没有找到任何解决方案。
  • 用回了低级别的 ReAct 流程,立即在对话质量准确性上超过了 LangChain 实现

LangChain 问题: 让简单事情变得相对复杂,而这种不必要的复杂性造成了一种「部落主义」,损害了整个新兴的人工智能生态系统。

  • LangChain 代码量与仅用官方 openai 库的代码量大致相同,估计 LangChain 合并了更多对象类,但代码优势并不明显。
  • LangChain 吹嘘的提示工程只是 f-strings,还有额外步骤。为什么需要使用这些 PromptTemplates 来做同样的事情呢?
  • 真正要做的:
    • 如何创建 Agent,结合迫切想要的 ReAct 工作流。而 LangChain示例里每个思想 / 行动 / 观察中都使用了自己的 API 调用 OpenAI,所以链条比想象的要慢。
  • LangChain 如何存储到目前为止的对话?

制作自己的 Python 软件包要比让 LangChain 来满足自己的需求容易得多

LangChain 确实也有很多实用功能,比如文本分割器和集成向量存储,这两种功能都是「用 PDF / 代码聊天」演示不可或缺的(在我看来这只是一个噱头)。

LangChain 组件

2 个核心功能为:

  • 1)LLM 模型与外部数据源进行连接。
  • 2)LLM 模型与环境交互,通过 Agent 使用工具。

LangChain包含六部分组件

  • img
  • Models、Prompts、Indexes、Memory、Chains、Agents。

LangChain主要支持6种组件:

  • Models模型,各种类型的模型和模型集成,比如GPT-4
  • Prompts提示,包括提示管理、提示优化和提示序列化
  • Memory记忆,用来保存和模型交互时的上下文状态
  • Indexes索引,用来结构化文档,以便和模型交互
  • Chains,一系列对各种组件的调用
  • Agents代理,决定模型采取哪些行动,执行并且观察流程,直到完成为止

框架

LangChain 框架示意图

Document Loaders and Utils

LangChain 的 Document Loaders 和 Utils 模块分别用于连接到数据源计算源

当使用loader加载器读取到数据源后,数据源需要转换成 Document 对象后,后续才能进行使用。

Document Loaders 的Unstructured 可以将这些原始数据源转换为可处理的文本。

The following document loaders are provided:

  • CSV Loader CSV文件
  • DataFrame Loader 从 pandas 数据帧加载数据
  • Diffbot 从 URL 列表中提取 HTML 文档,并将其转换为我们可以在下游使用的文档格式
  • Directory Loader 加载目录中的所有文档
  • EverNote 印象笔记
  • Git 从 Git 存储库加载文本文件
  • Google Drive Google网盘
  • HTML HTML 文档
  • Markdown
  • Notebook 将 .ipynb 笔记本中的数据加载为适合 LangChain 的格式
  • Notion
  • PDF
  • PowerPoint
  • Unstructured File Loader 使用Unstructured加载多种类型的文件,目前支持加载文本文件、powerpoints、html、pdf、图像等
  • URL 加载 URL 列表中的 HTML 文档内容
  • Word Documents

Text Spltters

文本分割用来分割文本。

为什么需要分割文本?

  • 因为每次不管把文本当作 prompt 发给 openai api ,还是使用 embedding 功能, 都是有字符限制的。

将一份300页的 pdf 发给 openai api,进行总结,肯定会报超过最大 Token 错。所以需要用文本分割器去分割 loader 进来的 Document。

  • 默认推荐的文本拆分器是 RecursiveCharacterTextSplitter
  • 默认情况以 [“\n\n”, “\n”, “ “, “”] 字符进行拆分。
  • 其它参数说明:
    • length_function 如何计算块的长度。默认只计算字符数,但在这里传递令牌计数器是很常见的。
    • chunk_size:块的最大大小(由长度函数测量)。
    • chunk_overlap:块之间的最大重叠。有一些重叠可以很好地保持块之间的一些连续性(例如,做一个滑动窗口)
  • CharacterTextSplitter 默认情况下以 separator=”\n\n”进行拆分
  • TiktokenText Splitter 使用OpenAI 的开源分词器包来估计使用的令牌
# This is a long document we can split up.
with open('../../../state_of_the_union.txt') as f:
    state_of_the_union = f.read()
from langchain.text_splitter import CharacterTextSplitter

text_splitter = CharacterTextSplitter(        
    separator = "\n\n",
    chunk_size = 1000,
    chunk_overlap  = 200,
    length_function = len,
)
metadatas = [{"document": 1}, {"document": 2}]
documents = text_splitter.create_documents([state_of_the_union, state_of_the_union], metadatas=metadatas)
print(texts[0])
# This is a long document we can split up.
with open('../../../state_of_the_union.txt') as f:
    state_of_the_union = f.read()
from langchain.text_splitter import TokenTextSplitter
text_splitter = TokenTextSplitter(chunk_size=10, chunk_overlap=0)
texts = text_splitter.split_text(state_of_the_union)
print(texts[0])

LangChain Embedding

模型拉到本地使用的好处:

  • 训练模型
  • 可用本地的 GPU
  • 有些模型无法在 HuggingFace 运行

LangChain Embedding示例

  • HuggingFace
from langchain.embeddings import HuggingFaceEmbeddings 

embeddings = HuggingFaceEmbeddings() 
text = "This is a test document." 
query_result = embeddings.embed_query(text) 
doc_result = embeddings.embed_documents([text])
  • llama-cpp
# !pip install llama-cpp-python
from langchain.embeddings import LlamaCppEmbeddings

llama = LlamaCppEmbeddings(model_path="/path/to/model/ggml-model-q4_0.bin")
text = "This is a test document."
query_result = llama.embed_query(text)
doc_result = llama.embed_documents([text])
  • OpenAI
from langchain.embeddings import OpenAIEmbeddings

embeddings = OpenAIEmbeddings()
text = "This is a test document."
query_result = embeddings.embed_query(text)
doc_result = embeddings.embed_documents([text])

(1)Models(模型):LLM选择

LangChain 本身不提供 LLM,提供通用的接口访问 LLM,可以很方便的更换底层的 LLM 以及自定义自己的 LLM。

Models(模型): 可选择不同的LLM与Embedding模型。可以直接调用 API 工作,也可以运行本地模型。

  • LLMs(大语言模型): 接收文本字符作为输入,返回的也是文本字符,类似 OpenAI 的 text-davinci-003
  • Chat Models 聊天模型: 由语言模型支持但将聊天消息列表作为输入并返回聊天消息的模型。一般使用的 ChatGPT 以及 Claude 为 Chat Models。
    • 聊天模型基于LLMs,不同的是它接收聊天消息作为输入,返回的也是聊天消息
    • 聊天消息是一种特定格式的数据,LangChain中支持四种消息: AIMessage, HumanMessage, SystemMessage ,ChatMessage ,需要按照角色把数据传递给模型,这部分在后面文章里再详细解释。
  • Text Embedding:用于文本的向量化表示。文本嵌入模型接收文本作为输入,返回的是浮点数列表. 设计用于与嵌入交互的类
    • 用于实现基于知识库的问答和semantic search,相比 fine-tuning 最大的优势:不用进行训练,并且可以实时添加新的内容,而不用加一次新的内容就训练一次,并且各方面成本要比 fine-tuning 低很多。
    • 例如,可调用OpenAI、Cohere、HuggingFace等Embedding标准接口,对文本向量化。
    • 两个方法:embed_documentsembed_query。最大区别在于接口不同:一种处理个文档,而另一种处理个文档。
    • 文本嵌入模型集成了如下的源:AzureOpenAI、Hugging Face Hub、InstructEmbeddings、Llama-cpp、OpenAI 等
  • HuggingFace Models

大语言模型(LLMs)是Models的核心,也是LangChain的基础组成部分,LLMs本质上是一个大型语言模型的包装器,通过该接口与各种大模型进行交互。

  • 这些模型包括OpenAI的GPT-3.5/4、谷歌的LaMDA/PaLM,Meta AI的LLaMA等。

LLMs 类的功能如下:

  • 支持多种模型接口,如 OpenAI、Hugging Face Hub、Anthropic、Azure OpenAI、GPT4All、Llama-cpp…
  • Fake LLM,用于测试
  • 缓存的支持,比如 in-mem(内存)、SQLite、Redis、SQL
  • 用量记录
  • 支持流模式(就是一个字一个字的返回,类似打字效果)

LangChain调用OpenAI的gpt-3.5-turbo大语言模型的简单示例

import os
from langchain.llms import OpenAI

openai_api_key = 'sk-******'
os.environ['OPENAI_API_KEY'] = openai_api_key

llm = OpenAI(model_name="gpt-3.5-turbo")
# llm = OpenAI(model_name="text-davinci-003", n=2, best_of=2)
print(llm("讲个笑话,很冷的笑话"))
# 为什么鸟儿会成为游泳高手?因为它们有一只脚比另一只脚更长,所以游起泳来不费力!(笑)
llm_result = llm.generate(["Tell me a joke", "Tell me a poem"])
llm_result.llm_output    # 返回 tokens 使用量
# ----- 使用模板 -----
from langchain import PromptTemplate

prompt_template = '''作为一个资深编辑,请针对 >>> 和 <<< 中间的文本写一段摘要。 
>>> {text} <<<
'''
prompt = PromptTemplate(template=prompt_template, input_variables=["text"])
print(prompt.format_prompt(text="我爱北京天安门"))

流式输出

流式输出

  • LangChain在支持代理封装ChatGPT接口的基础上,也同样地把ChatGPT API接口的流式数据返回集成了进来
from langchain.llms import OpenAI
from langchain.chat_models import ChatOpenAI, ChatAnthropic
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler # 流式
from langchain.schema import HumanMessage
# streaming
llm = OpenAI(streaming=True, callbacks=[StreamingStdOutCallbackHandler()], temperature=0)
resp = llm("Write me a song about sparkling water.")

缓存

缓存LLM的结果

  • 调用ChatGPT的API接口往往会存在网络延时的情况
  • 为了更优雅的实现LLM语言生成模型,LangChain同时提供了数据缓存的接口如果用户问了同样的问题,LangChain支持直接将所缓存的数据直接响应
import langchain
from langchain.cache import InMemoryCache # 启动缓存
langchain.llm_cache = InMemoryCache()

# To make the caching really obvious, lets use a slower model.
llm = OpenAI(model_name="text-davinci-002", n=2, best_of=2)
GPTCache

LLM 缓存工具

# [GPTCache : A Library for Creating Semantic Cache for LLM Queries](https://github.com/zilliztech/GPTCache)
from gptcache import cache
from gptcache.adapter import openai

cache.init()
cache.set_openai_key()

异步返回

异步返回

  • 构建复杂的LLM模型调用链时,往往存在接口的多次调用,而且并不能保证接口的实时性返回
  • 这时可以使用接口异步返回的模型来提升功能的服务质量,系统的性能
import time
import asyncio # 异步

from langchain.llms import OpenAI

def generate_serially():
    llm = OpenAI(temperature=0.9)
    for _ in range(10):
        resp = llm.generate(["Hello, how are you?"])
        print(resp.generations[0][0].text)

async def async_generate(llm):
    resp = await llm.agenerate(["Hello, how are you?"])
    print(resp.generations[0][0].text)

async def generate_concurrently():
    llm = OpenAI(temperature=0.9)
    tasks = [async_generate(llm) for _ in range(10)]
    await asyncio.gather(*tasks)

s = time.perf_counter()
# If running this outside of Jupyter, use asyncio.run(generate_concurrently())
await generate_concurrently()
elapsed = time.perf_counter() - s
print('\033[1m' + f"Concurrent executed in {elapsed:0.2f} seconds." + '\033[0m')

s = time.perf_counter()
generate_serially()
elapsed = time.perf_counter() - s
print('\033[1m' + f"Serial executed in {elapsed:0.2f} seconds." + '\033[0m')

多模型融合

整合多个模型

  • 作为LangChain中的核心模块,LangChain不止支持简单的LLM,从简单的文本生成功能、会话聊天功能以及文本向量化功能均集成,并且将其封装成一个一个的链条节点
  • (1)大语言模型
  • (2)聊天模型: OpenAI所提供的多角色聊天,允许用户设定信息归属不同的角色,从而丰富用户的聊天背景,构造出更加拟人化的聊天效果
  • (3)语言向量化模型

OpenAI

import os
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
from langchain.llms import OpenAI
from langchain import PromptTemplate, LLMChain
# 模板解析
template = """Question: {question}
Answer: Let's think step by step."""
prompt = PromptTemplate(template=template, input_variables=["question"])

llm = OpenAI()
llm_chain = LLMChain(prompt=prompt, llm=llm)
question = "What NFL team won the Super Bowl in the year Justin Beiber was born?"
llm_chain.run(question)

角色设置

from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    AIMessagePromptTemplate,
    HumanMessagePromptTemplate,
)
from langchain.schema import (
    AIMessage,
    HumanMessage,
    SystemMessage
)

chat = ChatOpenAI(temperature=0)

messages = [
    SystemMessage(content="You are a helpful assistant that translates English to French."),
    HumanMessage(content="Translate this sentence from English to French. I love programming.")
]
chat(messages)

模板化编排

template="You are a helpful assistant that translates {input_language} to {output_language}."
system_message_prompt = SystemMessagePromptTemplate.from_template(template)
human_template="{text}"
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)

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

# get a chat completion from the formatted messages
chat(chat_prompt.format_prompt(input_language="English", output_language="French", text="I love programming.").to_messages())

Azure OpenAI

import os
os.environ["OPENAI_API_TYPE"] = "azure"
os.environ["OPENAI_API_VERSION"] = "2022-12-01"
os.environ["OPENAI_API_BASE"] = "..."
os.environ["OPENAI_API_KEY"] = "..."
# Import Azure OpenAI
from langchain.llms import AzureOpenAI
# Create an instance of Azure OpenAI
# Replace the deployment name with your own
llm = AzureOpenAI(
    deployment_name="td2",
    model_name="text-davinci-002",
)
# Run the LLM
llm("Tell me a joke")

Hugging Face Hub

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

from langchain import HuggingFaceHub

repo_id = "google/flan-t5-xl" # See https://huggingface.co/models?pipeline_tag=text-generation&sort=downloads for some other options
llm = HuggingFaceHub(repo_id=repo_id, model_kwargs={"temperature":0, "max_length":64})

from langchain import PromptTemplate, LLMChain

template = """Question: {question}
Answer: Let's think step by step."""
prompt = PromptTemplate(template=template, input_variables=["question"])
llm_chain = LLMChain(prompt=prompt, llm=llm)
question = "Who won the FIFA World Cup in the year 1994? "
print(llm_chain.run(question))

(2)Prompts(提示语): 模板化

通常作为输入传递给模型的信息被称为提示

  • 提示可以是文本字符,也可以是文件图片甚至视频
  • LangChain目前只支持字符形式的提示。

提示一般不是硬编码的形式写在代码里,而是由模板用户输入来生成,LangChain提供多个类和方法来构建提示。

  • 提示模板
    • 提示模板是一种生成提示的方式,包含一个带有可替换内容的模板,从用户那获取一组参数并生成提示
    • 提示模板用来生成LLMs的提示,最简单的使用场景,比如“我希望你扮演一个代码专家的角色,告诉我这个方法的原理{code}”。
  • 聊天提示模板
    • 聊天模型接收聊天消息作为输入,再次强调聊天消息和普通字符是不一样的,聊天提示模板的作用就是为聊天模型生成提示
  • 示例选择器
    • 示例选择器是一个高级版的数据筛选器
  • 输出解析器
    • 由于模型返回的是文本字符,输出解析器可以把文本转换成结构化数据

Prompts(提示语): 管理LLM输入

  • PromptTemplate 负责构建此输入
  • LangChain 提供了可用于格式化输入和许多其他实用程序的提示模板。

当用户与大语言模型对话时,用户内容即Prompt(提示语)。

  • 如果用户每次输入的Prompt中包含大量的重复内容,生成一个Prompt模板,将通用部分提取出来,用户输入输入部分作为变量。

Prompt模板十分有用

  • 利用langchain构建专属客服助理,并且明确告诉其只回答知识库(产品介绍、购买流程等)里面的知识,其他无关的询问,只回答“我还没有学习到相关知识”。
  • 这时可利用Prompt模板对llm进行约束。

调用LangChain的PromptTemplate

from langchain import PromptTemplate

# 【无变量模板】An example prompt with no input variables
no_input_prompt = PromptTemplate(input_variables=[], template="Tell me a joke.")
no_input_prompt.format()
# -> "Tell me a joke."
# ① 【有变量模板】
name_template = """
我想让你成为一个起名字的专家。给我返回一个名字的名单. 名字寓意美好,简单易记,朗朗上口.
关于{name_description},好听的名字有哪些?
"""
# 创建一个prompt模板
prompt_template = PromptTemplate(input_variables=["name_description"], template=name_template)
description = "男孩名字"
print(prompt_template.format(name_description=description))
# 我想让你成为一个起名字的专家。给我返回一个名字的名单. 名字寓意美好,简单易记,朗朗上口.关于男孩名字,好听的名字有哪些?
# ②【多变量模板】
# An example prompt with multiple input variables
multiple_input_prompt = PromptTemplate(
    input_variables=["adjective", "content"],
    template="Tell me a {adjective} joke about {content}."
)
multiple_input_prompt.format(adjective="funny", content="chickens")
# -> "Tell me a funny joke about chickens."

提出多个问题,两种方法:

  1. 使用generate方法遍历所有问题,逐个回答。
  2. 将所有问题放入单个提示中,这仅适用于更高级的LLMs。
# ③ 【多输入】
qs = [ # Text only
    {'question': "Which NFL team won the Super Bowl in the 2010 season?"},
    {'question': "If I am 6 ft 4 inches, how tall am I in centimeters?"},
    {'question': "Who was the 12th person on the moon?"},
    {'question': "How many eyes does a blade of grass have?"}
]
res = llm_chain.generate(qs)
# res = LLMResult(generations = [[Generation(text ='green bay packers', generation_info = None)], [Generation(text ='184', generation_info = None)], [Generation(text ='john glenn', generation_info = None)], [Generation(text ='one', generation_info = None)]], llm_output = None)

FewShot PromptTemplate

  • 模板可以根据语料库中的内容进行匹配,最终可按照特定的格式匹配出样例中的内容。
from langchain.prompts.few_shot import FewShotPromptTemplate
from langchain.prompts.prompt import PromptTemplate

examples = [
  {
    "question": "Who lived longer, Muhammad Ali or Alan Turing?",
    "answer":
"""
Are follow up questions needed here: Yes.
Follow up: How old was Muhammad Ali when he died?
Intermediate answer: Muhammad Ali was 74 years old when he died.
Follow up: How old was Alan Turing when he died?
Intermediate answer: Alan Turing was 41 years old when he died.
So the final answer is: Muhammad Ali
"""
  },
  {
    "question": "When was the founder of craigslist born?",
    "answer":
"""
Are follow up questions needed here: Yes.
Follow up: Who was the founder of craigslist?
Intermediate answer: Craigslist was founded by Craig Newmark.
Follow up: When was Craig Newmark born?
Intermediate answer: Craig Newmark was born on December 6, 1952.
So the final answer is: December 6, 1952
"""
  }
]

example_prompt = PromptTemplate(input_variables=["question", "answer"], template="Question: {question}\n{answer}")
print(example_prompt.format(**examples[0]))
=========================
Question: Who lived longer, Muhammad Ali or Alan Turing?

Are follow up questions needed here: Yes.
Follow up: How old was Muhammad Ali when he died?
Intermediate answer: Muhammad Ali was 74 years old when he died.
Follow up: How old was Alan Turing when he died?
Intermediate answer: Alan Turing was 41 years old when he died.
So the final answer is: Muhammad Ali

(3)Indexes(索引):文档结构化

Indexes(索引):文档结构化方式, 以便LLM更好的交互

  • 索引是指对文档进行结构化的方法,以便LLM能够更好的与之交互。

最常见的使用场景是文档检索,接收用户查询,返回最相关的文档。

  • 注意: 索引也能用在除了检索外的其他场景,同样检索除了索引外也有其他的实现方式。

索引一般和检索非结构化数据(比如文本文档)相关,LangChain支持的主要索引类型如下,都是围绕着向量数据库的。

该组件主要包括:Document Loaders(文档加载器)、Text Splitters(文本拆分器)、VectorStores(向量存储器)以及Retrievers(检索器)。

  • 文本检索器:将特定格式数据转换为文本。输入可以是 pdf、word、csv、images 等。
  • 文本拆分器:将长文本拆分成小的文本块,便于LLM模型处理。
    • 由于模型处理数据时,对输入长度有限制,因此需要对长文本进行分块
    • 不同语言模型对块的大小定义不同,比如OpenAI的GPT对分块的长度通过token大小来限制,比如GPT-3.5是4096,即这个分块所包含的Token数量不能超过4096。
    • 一般的分块方法:首先,对长文本进行断句,即分成一句一句话。然后,计算每句话包含的token数量,并从第一句话开始往后依次累加,直到达到指定数量,组成为1个分块。依次重复上述操作。比如按照字母切分的Character,按照token切分的Tiktoken等。
  • 向量存储器:存储提取的文本向量,包括Faiss、Milvus、Pinecone、Chroma等。
  • 向量检索器:通过用户输入的文本,检索器负责从底库中检索出特定相关度的文档。度量准则包括余弦距离、欧式距离等。

Document Loaders(文档加载器)

LangChain 通过 Loader 加载外部的文档,转化为标准的 Document 类型。

Document 类型主要包含两个属性:

  • page_content 包含该文档的内容。
  • meta_data 为文档相关的描述性数据,类似文档所在的路径等。

LangChain 目前支持结构化、非结构化以及公开以及私有的各种数据

  非结构化 unstructed 结构化 structured
公开 public Wikipedia, youtube, bilibili, arXiv, twitter, imdb… Huggingface Datasets, OpenWeather
私有 proprietary ppt,word, Note, snapchat, github, jupyter.. excel,pandas,spark…

  • 数据库介于私有区,既有结构化(mysql)又有非结构化(redis,es..)

数据源加载

# 🎨 加载B站(BiliBili)视频数据
#!pip install bilibili-api
from langchain.document_loaders.bilibili import BiliBiliLoader
loader = BiliBiliLoader(
    ["https://www.bilibili.com/video/BV1xt411o7Xu/"]
)
loader.load()

# 🎨 加载CSV数据
from langchain.document_loaders.csv_loader import CSVLoader

loader = CSVLoader(file_path='./example_data/mlb_teams_2012.csv')

data = loader.load()

# 🎨 加载Email数据
#!pip install unstructured
from langchain.document_loaders import UnstructuredEmailLoader
loader = UnstructuredEmailLoader('example_data/fake-email.eml')
data = loader.load()

# 🎨 加载电子书Epub数据
#!pip install pandocs
from langchain.document_loaders import UnstructuredEPubLoader
loader = UnstructuredEPubLoader("winter-sports.epub", mode="elements")
data = loader.load()

# 🎨 加载Git数据
# !pip install GitPython

from git import Repo
repo = Repo.clone_from(
    "https://github.com/hwchase17/langchain", to_path="./example_data/test_repo1"
)
branch = repo.head.reference

from langchain.document_loaders import GitLoader
loader = GitLoader(repo_path="./example_data/test_repo1/", branch=branch)
data = loader.load()

# 🎨 加载HTML数据
from langchain.document_loaders import UnstructuredHTMLLoader

loader = UnstructuredHTMLLoader("example_data/fake-content.html")

data = loader.load()

# 🎨 加载Image图片数据
#!pip install pdfminer

from langchain.document_loaders.image import UnstructuredImageLoader
loader = UnstructuredImageLoader("layout-parser-paper-fast.jpg")
data = loader.load()

# 🎨 加载Word文档数据
from langchain.document_loaders import Docx2txtLoader
loader = Docx2txtLoader("example_data/fake.docx")

data = loader.load()
# [Document(page_content='Lorem ipsum dolor sit amet.', metadata={'source': 'example_data/fake.docx'})]

# 🎨 加载PDF文件数据
# !pip install pypdf

from langchain.document_loaders import PyPDFLoader

loader = PyPDFLoader("example_data/layout-parser-paper.pdf")
pages = loader.load_and_split()
pages[0]

Text Splitters(文本拆分器)

LLM 一般都会限制上下文窗口的大小,有 4k、16k、32k 等。针对大文本就需要进行文本分割,常用的文本分割器为 RecursiveCharacterTextSplitter,可以通过 separators 指定分隔符。其先通过第一个分隔符进行分割,不满足大小的情况下迭代分割。

文本分割主要有 2 个考虑:

  • 1)将语义相关的句子放在一块形成一个 chunk。一般根据不同的文档类型定义不同的分隔符,或者可以选择通过模型进行分割。
  • 2)chunk 控制在一定的大小,可以通过函数去计算。默认通过 len 函数计算,模型内部一般都是使用 token 进行计算。token 通常指的是将文本或序列数据划分成的小的单元或符号,便于机器理解和处理。使用 OpenAI 相关的大模型,可以通过 tiktoken 包去计算其 token 大小。
from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    model_name="gpt-3.5-turb
    allowed_special="all",
    separators=["\n\n", "\n", "", ""],
    chunk_size=7000,
    chunk_overlap=0
)
docs = text_splitter.create_documents(["文本在这里"])
print(docs)

示例

# pip install chromadb
from langchain.chains import RetrievalQA
from langchain.llms import OpenAI
# 指定要使用的文档加载器
from langchain.document_loaders import TextLoader
documents = TextLoader('../state_of_the_union.txt', encoding='utf8')
# 接下来将文档拆分成块。
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)
# 然后我们将选择我们想要使用的嵌入。
from langchain.embeddings import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
# 我们现在创建 vectorstore 用作索引。
from langchain.vectorstores import Chroma
db = Chroma.from_documents(texts, embeddings)
# 这就是创建索引。然后,我们在检索器接口中公开该索引。
retriever = db.as_retriever()
# 创建一个链并用它来回答问题!
qa = RetrievalQA.from_chain_type(llm=OpenAI(), chain_type="stuff", retriever=retriever)
query = "What did the president say about Ketanji Brown Jackson"
qa.run(query)

VectorStores(向量存储器)

通过 Text Embedding models,将文本转为向量,可以进行语义搜索,在向量空间中找到最相似的文本片段。

  • 目前支持常用的向量存储有 Faiss、Chroma 等。
  • Embedding 模型支持 OpenAIEmbeddings、HuggingFaceEmbeddings 等。通过 HuggingFaceEmbeddings 加载本地模型可以节省 embedding 的调用费用。
#通过cache_folder加载本地模型
embeddings = HuggingFaceEmbeddings(model_name="text2vec-base-chinese", cache_folder="本地模型地址")
embeddings = embeddings_model.embed_documents(
    [
        "我爱北京天安门!",
        "Hello world!"
    ]
)

Retrievers(检索器)

Retriever 接口用于根据非结构化的查询获取文档,一般情况下是文档存储在向量数据库中。可以调用 get_relevant_documents 方法来检索与查询相关的文档。

Retrievers

检索器接口是一个通用接口,可以轻松地将文档与语言模型结合起来。

  • 此接口公开了一个 get_relevant_documents 方法,该方法接受一个查询(一个字符串)并返回一个文档列表。

一般来说,用的都是 VectorStore Retriever。

  • 此检索器由 VectorStore 大力支持。一旦你构造了一个 VectorStore,构造一个检索器就很容易了。
# # pip install faiss-cpu
from langchain.chains import RetrievalQA
from langchain.llms import OpenAI
from langchain.document_loaders import DirectoryLoader
# 加载文件夹中的所有txt类型的文件,并转成 document 对象
loader = DirectoryLoader('./data/', glob='**/*.txt')
documents = loader.load()
# 接下来,我们将文档拆分成块。
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)
# 然后我们将选择我们想要使用的嵌入。
from langchain.embeddings import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
from langchain.vectorstores import FAISS
db = FAISS.from_documents(texts, embeddings)
query = "未入职同事可以出差吗"
docs = db.similarity_search(query)
docs_and_scores = db.similarity_search_with_score(query)
print(docs)

retriever = db.as_retriever()	# 最大边际相关性搜索 mmr
# retriever = db.as_retriever(search_kwargs={"k": 1})	# 搜索关键字
docs = retriever.get_relevant_documents("未入职同事可以出差吗")
print(len(docs))

# db.save_local("faiss_index")
# new_db = FAISS.load_local("faiss_index", embeddings)
# docs = new_db.similarity_search(query)
# docs[0]

(4)Chains(链条):组合链路

Langchain 通过 chain 将各个组件进行链接,以及 chain 之间进行链接,用于简化复杂应用程序的实现。

Chains(链条):将LLM与其他组件结合, 允许将多个组件组合在一起以创建一个单一的、连贯的应用程序。

  • 把一个个独立的组件链接在一起,LangChain名字的由来
  • Chain 可理解为任务。一个 Chain 就是一个任务,也可以像链条一样,逐个执行多个链

Chain提供了一种将各种组件统一到应用程序中的方法。

  • 例如,创建一个Chain,接受来自用户的输入,并通过PromptTemplate将其格式化,然后将格式化的输出传入到LLM模型中。
  • 通过多个Chain与其他部件结合,可生成复杂的链,完成复杂的任务。
  • Chains示意图

LangChain中,主要有下面几种链,LLMChainSequential Chain 以及 Route Chain,其中最常用的是LLMChain。

  • LLMChain 最基本的链
    • LLMChain由 PromptTemplate模型和可选的输出解析器组成。
    • 链接收多个输入变量,使用PromptTemplate生成提示,传递给模型,最后使用输出解析器把模型返回值转换成最终格式。
  • 索引相关链
    • 和索引交互,把自己的数据和LLMs结合起来,最常见的例子是根据文档来回答问题。
  • 提示选择器
    • 为不同模型生成不同的提示

LLM与其他组件结合,创建不同应用,一些例子:

  • 将LLM与提示模板相结合
  • 第一个 LLM 的输出作为第二个 LLM 的输入, 顺序组合多个 LLM
  • LLM与外部数据结合,比如,通过langchain获取youtube视频链接,通过LLM视频问答
  • LLM与长期记忆结合,比如聊天机器人

LLMChain

LLMChain,由 PromptTemplate、LLM 和 OutputParser 组成。LLM 的输出一般为文本,OutputParser 用于让 LLM 结构化输出并进行结果解析,方便后续的调用。

from langchain import LLMChain

llm_chain = LLMChain(prompt=prompt, llm=llm)
question = "Can Barack Obama have a conversation with George Washington?"
print(llm_chain.run(question))

LLMChain是一个简单的链,它接受一个提示模板,用用户输入格式化它并返回来自 LLM 的响应。

from langchain.llms import OpenAI
from langchain.docstore.document import Document
import requests
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.text_splitter import CharacterTextSplitter
from langchain.prompts import PromptTemplate
import pathlib
import subprocess
import tempfile
"""
生成对以前撰写的博客文章有理解的博客文章,或者可以参考产品文档的产品教程
"""

source_chunks = ""
search_index = Chroma.from_documents(source_chunks, OpenAIEmbeddings())

from langchain.chains import LLMChain
prompt_template = """Use the context below to write a 400 word blog post about the topic below:
    Context: {context}
    Topic: {topic}
    Blog post:"""

PROMPT = PromptTemplate(
    template=prompt_template, input_variables=["context", "topic"]
)

llm = OpenAI(temperature=0)

chain = LLMChain(llm=llm, prompt=PROMPT)

def generate_blog_post(topic):
    docs = search_index.similarity_search(topic, k=4)
    inputs = [{"context": doc.page_content, "topic": topic} for doc in docs]
    print(chain.apply(inputs))
generate_blog_post("environment variables")
# 附加示例
llm_chain = LLMChain(prompt=prompt, llm=llm)
comment = "京东物流没的说,速度态度都是杠杠滴!这款路由器颜值贼高,怎么说呢,就是泰裤辣!这线条,这质感,这速度,嘎嘎快!以后妈妈再也不用担心家里的网速了!"
result = llm_chain.run(comment)
data = output_parser.parse(result)
print(f"type={type(data)}, keyword={data['keyword']}, emotion={data['emotion']}")

Sequential Chain (一串LLMChain)

SequentialChains 按预定义顺序执行的链。SimpleSequentialChain 为顺序链的最简单形式,其中每个步骤都有一个单一的输入 / 输出,一个步骤的输出是下一个步骤的输入。SequentialChain 为顺序链更通用的形式,允许多个输入 / 输出。

执行多个 LLMChain

  • 顺序链是按预定义顺序执行其链接的链。
  • 使用SimpleSequentialChain,其中每个步骤都有一个输入/输出,一个步骤的输出是下一个步骤的输入。
from langchain.llms import OpenAI
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain.chains import SimpleSequentialChain

# location 链
llm = OpenAI(temperature=1)
template = """Your job is to come up with a classic dish from the area that the users suggests.
% USER LOCATION
{user_location}

YOUR RESPONSE:
"""
prompt_template = PromptTemplate(input_variables=["user_location"], template=template)
location_chain = LLMChain(llm=llm, prompt=prompt_template)

# meal 链
template = """Given a meal, give a short and simple recipe on how to make that dish at home.
% MEAL
{user_meal}

YOUR RESPONSE:
"""
prompt_template = PromptTemplate(input_variables=["user_meal"], template=template)
meal_chain = LLMChain(llm=llm, prompt=prompt_template)

# 通过 SimpleSequentialChain 串联起来,第一个答案会被替换第二个中的user_meal,然后再进行询问
overall_chain = SimpleSequentialChain(chains=[location_chain, meal_chain], verbose=True)
review = overall_chain.run("Rome")

Router Chain

RouterChain 根据输入动态选择下一个链,每条链处理特定类型的输入。

RouterChain 由两个组件组成:

  • 1)路由器链本身,负责选择要调用的下一个链,主要有 2 种 RouterChain,其中 LLMRouterChain 通过 LLM 进行路由决策,EmbeddingRouterChain 通过向量搜索的方式进行路由决策。
  • 2)目标链列表,路由器链可以路由到的子链。

初始化 RouterChain 以及 destination_chains 完成后,通过 MultiPromptChain 将两者结合起来使用。

Documents Chain

下面4 种 Chain 主要用于 Document 处理,在基于文档生成摘要、基于文档的问答等场景中经常会用到,在后续的落地实践里也会有所体现。

Stuff

StuffDocumentsChain 最简单直接,是将所有获取到的文档作为 context 放入到 Prompt 中,传递到 LLM 获取答案。

这种方式可以完整保留上下文,调用 LLM 的次数也比较少,建议能使用 stuff 的就使用这种方式。其适合文档拆分的比较小,一次获取文档比较少的场景,不然容易超过 token 的限制。

Refine

RefineDocumentsChain 是通过迭代更新方式获取答案。先处理第一个文档,作为 context 传递给 llm,获取中间结果 intermediate answer。然后将第一个文档的中间结果以及第二个文档发给 llm 进行处理,后续的文档类似处理。

Refine 这种方式能部分保留上下文,以及 token 的使用能控制在一定范围。

MapReduce

MapReduceDocumentsChain 先通过 LLM 对每个 document 进行处理,然后将所有文档的答案在通过 LLM 进行合并处理,得到最终的结果。

MapReduce 的方式将每个 document 单独处理,可以并发进行调用。但是每个文档之间缺少上下文。

MapRerank

MapRerankDocumentsChain 和 MapReduceDocumentsChain 类似,先通过 LLM 对每个 document 进行处理,每个答案都会返回一个 score,最后选择 score 最高的答案。

MapRerank 和 MapReduce 类似,会大批量的调用 LLM,每个 document 之间是独立处理。

(5)Agents(智能体):其他工具

“链”可以帮助将一系列 LLM 调用链接在一起。然而,在某些任务中,调用顺序通常是不确定的。

  • 有些应用并不是一开始就确定调用哪些模型,而是依赖于用户输入

LangChain 库提供了代理“Agents”,根据未知输入而不是硬编码来决定下一步采取的行动。

Agent 字面含义是代理,如果说 LLM 是大脑,Agent 就是代理大脑使用工具 Tools。

目前大模型一般都存在知识过时逻辑计算能力低等问题,通过 Agent 访问工具,可以去解决这些问题。这个领域特别活跃,诞生了类似 AutoGPTBabyAGIAgentGPT 等一堆优秀的项目。传统使用 LLM,需要给定 Prompt 一步一步的达成目标,通过 Agent 是给定目标,其会自动规划并达到目标。

Agents通常由三个部分组成:ActionObservationDecision

  • Action是代理执行的操作
  • Observation是代理接收到的信息
  • Decision是代理基于ActionObservation做出的决策。

Agent 核心组件

Agent 核心组件

  • Agent代理,负责调用 LLM 以及决定下一步的 Action。其中 LLM 的 prompt 必须包含 agent_scratchpad 变量,记录执行的中间过程
  • Tools工具,Agent 可以调用的方法。LangChain 已有很多内置的工具,也可以自定义工具。注意 Tools 的 description 属性,LLM 会通过描述决定是否使用该工具。
  • ToolKits工具集,为特定目的的工具集合。类似 Office365、Gmail 工具集等
  • Agent ExecutorAgent 执行器,负责进行实际的执行。

Agent 使用LLM来确定要采取哪些行动以及按什么顺序采取的行动。操作可以使用工具并观察其输出,也可以返回用户。

创建agent时的参数:

  • LLM:为代理提供动力的语言模型。
  • 工具:执行特定职责的功能, 方便模型和其他资源交互
    • 比如:Google搜索,数据库查找,Python Repl。工具的接口当前是一个函数,将字符串作为输入,字符串作为输出。
  • 工具集
    • 解决特定问题的工具集合
  • 代理:highest level API、custom agent. 要使用的代理。围绕模型的包装器,接收用户输入,决定模型的行为
  • 代理执行器
    • 代理和一组工具,调用代理
# Create RetrievalQA Chain
from langchain.chains import RetrievalQA
retrieval_qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=docsearch.as_retriever())

# Create an Agent
from langchain.agents import initialize_agent, Tool

tools = [
    Tool(
        name="Example QA System",
        func=retrieval_qa.run,
        description="Example description of the tool."
    ),
]
# Agent 的初始化, 除了 llm、tools 等参数,还需要指定 AgentType。
# 通过 agent.agent.llm_chain.prompt.template 方法,获取其推理决策所使用的模板
agent = initialize_agent(tools, llm, agent="zero-shot-react-description", verbose=True)

# Use Agent
agent.run("Ask a question related to the documents")

Agent 的类型

Agent Type

  • zero-shot-react-description: 只考虑当前的操作,不会记录以及参考之前的操作。react 表明通过 ReAct 框架进行推理,description 表明通过工具的 description 进行是否使用的决策。
  • chat-conversational-react-description:
  • conversational-react-description:
  • react-docstore:
  • self-ask-with-search 等,类似 chat-conversational-react-description 通过 memory 记录之前的对话,应答会参考之前的操作。

自定义 Tool

Agents(智能体):访问其他工具

Agents是LLM与工具之间的接口,Agents用来确定任务与工具。

一般的Agents执行任务过程:

  • a. 首先,接收用户输入,并转化为PromptTemplate
  • b. 其次,Agents通过调用LLM输出action,并决定使用哪种工具执行action
  • c. 最后,Agents调用工具完成action任务
  • agent示意图

Agents可以调用那些工具完成任务?

工具 描述
搜索 调用谷歌浏览器或其他浏览器搜索指定内容
终端 在终端中执行命令,输入应该是有效的命令,输出将是运行该命令的任何输出
Wikipedia 从维基百科生成结果
Wolfram-Alpha WA 搜索插件——可以回答复杂的数学、物理或任何查询,将搜索查询作为输入。
Python REPL 用于评估和执行 Python 命令的 Python shell。它以 python 代码作为输入并输出结果。输入的 python 代码可以从 LangChain 中的另一个工具生成

Agent通过调用wikipedia工具,对用户提出的问题回答。尽管gpt-3.5功能强大,但是其知识库截止到2021年9月,因此,agent调用wikipedia外部知识库对用户问题回答。回答过程如下:

  • a. 分析用户输入问题,采取的Action为通过Wikipedia实现,并给出了Action的输入
  • b. 根据分析得到了最相关的两页,并进行了总结
  • c. 对最后的内容进一步提炼,得到最终答案
import os
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.llms import OpenAI

openai_api_key = 'sk-F9xxxxxxx55q8YgXb6s5dJ1A4LjA'
os.environ['OPENAI_API_KEY'] = openai_api_key
llm = OpenAI(temperature=0)
tools = load_tools(["wikipedia","llm-math"], llm=llm)
agent = initialize_agent(tools, llm, agent="zero-shot-react-description", verbose=True)
print(agent.run("列举spaceX星舰在2022年后的发射记录?"))

多种方式可以自定义 Tool,最简单的方式是通过 @tool 装饰器,将一个函数转为 Tool。注意函数必须得有 docString,其为 Tool 的描述。

from azure_chat_llm import llm
from langchain.agents import load_tools, initialize_agent, tool
from langchain.agents.agent_types import AgentType
from datetime import date

@tool
def time(text: str) -> str:
    """
    返回今天的日期。
    """
    return str(date.today())

tools = load_tools(['llm-math'], llm=llm)
tools.append(time)
agent_math = initialize_agent(agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
                                   tools=tools,
                                   llm=llm,
                                   verbose=True)
print(agent_math("计算45 * 54"))
print(agent_math("今天是哪天?"))

(6)Memory(记忆):

模型是无状态的,不保存上一次交互时的数据

  • OpenAI的API服务没有上下文概念,而chatGPT是额外实现了上下文功能。

正常情况下 Chain 无状态的,每次交互都是独立的,无法知道之前历史交互的信息。

对于像聊天机器人这样的应用程序,需要记住以前的对话内容。

  • 但默认情况下,LLM对历史内容没有记忆功能。LLM的输出只针对用户当前的提问内容回答。
  • 为解决这个问题,Langchain提供了记忆组件,用来管理与维护历史对话内容。
  • memory示意图

LangChain 使用 Memory 组件保存和管理历史消息,这样可以跨多轮进行对话,在当前会话中保留历史会话的上下文。Memory 组件支持多种存储介质,可以与 Monogo、Redis、SQLite 等进行集成,以及简单直接形式就是 Buffer Memory。常用的 Buffer Memory 有

  • 1)ConversationSummaryMemory :以摘要的信息保存记录
  • 2)ConversationBufferWindowMemory:以原始形式保存最新的 n 条记录
  • 3)ConversationBufferMemory:以原始形式保存所有记录通过查看 chain 的 prompt,可以发现 {history} 变量传递了从 memory 获取的会话上下文。下面的示例演示了 Memory 的使用方式,可以很明细看到,答案是从之前的问题里获取的。

langchain提供不同的Memory组件完成内容记忆,下面列举四种:

  • ConversationBufferMemory:记住全部对话内容。这是最简单的内存记忆组件,它的功能是直接将用户和机器人之间的聊天内容记录在内存中。img
  • ConversationBufferWindowMemory:记住最近k轮的聊天内容。与之前的ConversationBufferMemory组件的差别是它增加了一个窗口参数,它的作用是可以指定保存多轮对话的数量。img
    • ​在该例子中设置了对话轮数k=2,即只能记住前两轮的内容,“我的名字”是在前3轮中的Answer中回答的,因此其没有对其进行记忆,所以无法回答出正确答案。
  • ConversationSummaryMemory:ConversationSummaryMemory它不会将用户和机器人之前的所有对话都存储在内存中。它只会存储一个用户和机器人之间的聊天内容的摘要,这样做的目的可能是为了节省内存开销和token的数量。
    • ConversationSummaryMemory第一轮对话: 你好,我是王老六
    • ConversationSummaryMemory第二轮对话: 你叫什么名字
    • 在第一轮对话完成后,Memory对第一轮对话的内容进行了总结,放到了摘要中。在第二轮对话中,LLM基于摘要与该轮的问题进行回答。
  • VectorStored-Backed Memory: 将所有之前的对话通过向量的方式存储到VectorDB(向量数据库)中,在每一轮新的对话中,会根据用户的输入信息,匹配向量数据库中最相似的K组对话。
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory

from azure_chat_llm import llm

memory = ConversationBufferMemory()
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)
print(conversation.prompt)
print(conversation.predict(input="我的姓名是tiger"))
print(conversation.predict(input="1+1=?"))
print(conversation.predict(input="我的姓名是什么"))

国内不少LLm团队采用langChain,集成llm本地化知识库

  • langChain,babyAGI 想做AGI生态,这个就有些力不从心了。autoGPT好一点,相对简单。
  • langChain,babyAGI的子模块,都是几百个。特别是langChain,模块库居然有600多张子模块map架构图

无需OpenAI API Key,构建个人化知识库的终极指南

构建知识库的主要流程:

  1. 加载文档
  2. 文本分割
  3. 构建矢量数据库
  4. 引入LLM
  5. 创建qa_chain,开始提问

LangChain 实践

文档生成总结

文档生成总结

  • 1)通过 Loader 加载远程文档
  • 2)通过 Splitter 基于 Token 进行文档拆分
  • 3)加载 summarize 链,链类型为 refine,迭代进行总结

3LangChain 落地实践
3.1 文档生成总结
1通过 Loader 加载远程文档

2通过 Splitter 基于 Token 进行文档拆分

3加载 summarize 链类型为 refine迭代进行总结

from langchain.prompts import PromptTemplate
from langchain.document_loaders import PlaywrightURLLoader
from langchain.chains.summarize import load_summarize_chain
from langchain.text_splitter import RecursiveCharacterTextSplitter
from azure_chat_llm import llm

loader = PlaywrightURLLoader(urls=["https://content.jr.jd.com/article/index.html?pageId=708258989"])
data = loader.load()

text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    model_name="gpt-3.5-turbo",
    allowed_special="all",
    separators=["\n\n", "\n", "。", ","],
    chunk_size=7000,
    chunk_overlap=0
)

prompt_template = '''
作为一个资深编辑,请针对 >>> 和 <<< 中间的文本写一段摘要。 
>>> {text} <<<
'''
refine_template = '''
作为一个资深编辑,基于已有的一段摘要:{existing_answer},针对 >>> 和 <<< 中间的文本完善现有的摘要。 
>>> {text} <<<
'''

PROMPT = PromptTemplate(template=prompt_template, input_variables=["text"])
REFINE_PROMPT = PromptTemplate(
    template=refine_template, input_variables=["existing_answer", "text"]
)

chain = load_summarize_chain(llm, chain_type="refine", question_prompt=PROMPT, refine_prompt=REFINE_PROMPT, verbose=False)

docs = text_splitter.split_documents(data)
result = chain.run(docs)
print(result)

基于外部文档的问答

基于外部文档的问答

  • 1)通过 Loader 加载远程文档
  • 2)通过 Splitter 基于 Token 进行文档拆分
  • 3)通过 FAISS 向量存储文档,embedding 加载 HuggingFace 的 text2vec-base-chinese 模型
  • 4)自定义 QA 的 prompt,通过 RetrievalQA 回答相关的问题
作者京东云
链接https://www.zhihu.com/question/609483833/answer/3146379316
来源知乎
著作权归作者所有商业转载请联系作者获得授权非商业转载请注明出处

from langchain.chains import RetrievalQA
from langchain.document_loaders import WebBaseLoader
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from langchain.prompts import PromptTemplate
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import FAISS

from azure_chat_llm import llm

loader = WebBaseLoader("https://in.m.jd.com/help/app/register_info.html")
data = loader.load()
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    model_name="gpt-3.5-turbo",
    allowed_special="all",
    separators=["\n\n", "\n", "。", ","],
    chunk_size=800,
    chunk_overlap=0
)
docs = text_splitter.split_documents(data)
#设置自己的模型路径
embeddings = HuggingFaceEmbeddings(model_name="text2vec-base-chinese", cache_folder="model")
vectorstore = FAISS.from_documents(docs, embeddings)

template = """请使用下面提供的背景信息来回答最后的问题。 如果你不知道答案,请直接说不知道,不要试图凭空编造答案。
回答时最多使用三个句子,保持回答尽可能简洁。 回答结束时,请一定要说"谢谢你的提问!"
{context}
问题: {question}
有用的回答:"""
QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context", "question"], template=template)

qa_chain = RetrievalQA.from_chain_type(llm, retriever=vectorstore.as_retriever(),
                                       return_source_documents=True,
                                       chain_type_kwargs={"prompt": QA_CHAIN_PROMPT})

result = qa_chain({"query": "用户注册资格"})
print(result["result"])
print(len(result['source_documents']))

LangChain + Milvus

from langchain.embeddings.openai import OpenAIEmbeddings # openai
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.chains.question_answering import load_qa_chain
from langchain.llms import OpenAI  # openai
import os

os.environ["OPENAI_API_KEY"] = "OPENAI_API_KEY"

# Question Answering Chain
# ① 加载文档
with open("../test.txt") as f:
    state_of_the_union = f.read()
# ② 文本分割
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0) # 指定分割器
texts = text_splitter.split_text(state_of_the_union) # 分割文本
embeddings = OpenAIEmbeddings() # 使用OpenAI的embedding服务
# ③ 构建适量数据库
docsearch = Chroma.from_texts(texts, embeddings, metadatas=[{"source": str(i)} for i in range(len(texts))]).as_retriever()
query = "What did the president say about Justice Breyer"
docs = docsearch.get_relevant_documents(query)
# ④ 引入LLM,创建qa_chain
chain = load_qa_chain(OpenAI(temperature=0), chain_type="stuff")
# ⑤ 开始提问
answer = chain.run(input_documents=docs, question=query)
print(answer)

以上构建依赖OpenAI,有第三方免费服务吗?

Huggingface开源AI模型构建本地知识库

  • 开源的google/flan-t5-xlAI模型
from langchain import HuggingFacePipeline
from langchain.chains import RetrievalQA
from langchain.chains.question_answering import load_qa_chain
from langchain.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import Chroma
from langchain.llms.base import LLM
import os

from transformers import AutoTokenizer, AutoModelForSeq2SeqLM, pipeline

os.environ["HUGGINGFACEHUB_API_TOKEN"] = 'HUGGINGFACEHUB_API_TOKEN'

# Document Loaders
loader = TextLoader('../example_data/test.txt', encoding='utf8')
documents = loader.load()

# Text Splitters
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)

# select embeddings
embeddings = HuggingFaceEmbeddings()

# create vectorstores
db = Chroma.from_documents(texts, embeddings)

# Retriever
retriever = db.as_retriever(search_kwargs={"k": 2})

query = "what is embeddings?"
docs = retriever.get_relevant_documents(query)

for item in docs:
    print("page_content:")
    print(item.page_content)
    print("source:")
    print(item.metadata['source'])
    print("---------------------------")

tokenizer = AutoTokenizer.from_pretrained("google/flan-t5-xl")
model = AutoModelForSeq2SeqLM.from_pretrained("google/flan-t5-xl")
pipe = pipeline(
    "text2text-generation",
    model=model,
    tokenizer=tokenizer,
    max_length=512,
    temperature=0,
    top_p=0.95,
    repetition_penalty=1.15
)

llm = HuggingFacePipeline(pipeline=pipe)

chain = load_qa_chain(llm, chain_type="stuff")
llm_response = chain.run(input_documents=docs, question=query)
print(llm_response)
print("done.")

集成了 Milvus 和 LangChain:参考

class VectorStore(ABC):
    """Interface for vector stores."""    
    @abstractmethod
    def add_texts(
        self,
        texts: Iterable[str],
        metadatas: Optional[List[dict]] = None,
        kwargs:Any,
    ) ->List[str]:
    """
      Run more texts through the embeddings and add to the vectorstore.
    """    
    @abstractmethod
    def similarity_search(self, query:str, k:int =4,kwargs: Any) -> List[Document]:
        """Return docs most similar to query."""
        def max_marginal_relevance_search(self, query: str, k: int = 4, fetch_k: int = 20) -> List[Document]:
        """Return docs selected using the maximal marginal relevance."""
        raise NotImplementedError

    @classmethod    
    @abstractmethod
    def from_texts(
        cls: Type[VST],
        texts: List[str],
        embedding: Embeddings,
        metadatas: Optional[List[dict]] = None,
        **kwargs: Any,
    ) -> VST:
        """Return VectorStore initialized from texts and embeddings."""

将 Milvus 集成到 LangChain 中,实现几个关键函数:add_texts()、similarity_search()、max_marginal_relevance_search()和 from_text()

将 Milvus 集成到 LangChain 中的确存在一些问题,最主要的是 Milvus 无法处理 JSON 文件。目前,只有两种解决方法:

  • 现有的 Milvus collection 上创建一个 VectorStore。
  • 基于上传至 Milvus 的第一个文档创建一个 VectorStore。

LangChain + Faiss + Ray 实践

【2023-5-29】Building an LLM open source search engine in 100 lines using LangChain and Ray

  • Building the index: Build a document index easily with Ray and Langchain
  • Build a document index 4-8x faster with Ray
  • Serving: Serve search queries with Ray and Langchain

LangChain+ChatGLM 本地问答

LangChain+ChatGLM 实现本地问答

ChatGLM-6B api部署:ChatGLM 集成进LangChain工具

  • api.py
  • 默认本地的 8000 端口,通过 POST 方法进行调用
pip install fastapi uvicorn
python api.py

效果

curl -X POST "http://{your_host}:8000" \
     -H 'Content-Type: application/json' \
     -d '{"prompt": "你好", "history": []}'
# 结果
{
  "response":"你好👋!我是人工智能助手 ChatGLM-6B,很高兴见到你,欢迎问我任何问题。",
  "history":[["你好","你好👋!我是人工智能助手 ChatGLM-6B,很高兴见到你,欢迎问我任何问题。"]],
  "status":200,
  "time":"2023-03-23 21:38:40"
}

封装 ChatGLM API到LangChain中

from langchain.llms.base import LLM
from langchain.llms.utils import enforce_stop_tokens
from typing import Dict, List, Optional, Tuple, Union

import requests
import json

class ChatGLM(LLM):
    max_token: int = 10000
    temperature: float = 0.1
    top_p = 0.9
    history = []

    def __init__(self):
        super().__init__()

    @property
    def _llm_type(self) -> str:
        return "ChatGLM"

    def _call(self, prompt: str, stop: Optional[List[str]] = None) -> str:
        # headers中添加上content-type这个参数,指定为json格式
        headers = {'Content-Type': 'application/json'}
        data=json.dumps({
          'prompt':prompt,
          'temperature':self.temperature,
          'history':self.history,
          'max_length':self.max_token
        })
        # print("ChatGLM prompt:",prompt)
        # 调用api
        response = requests.post("{your_host}/api",headers=headers,data=data)
		# print("ChatGLM resp:",response)
        if response.status_code!=200:
          return "查询结果错误"
        resp = response.json()
        if stop is not None:
            response = enforce_stop_tokens(response, stop)
        self.history = self.history+[[None, resp['response']]]
        return resp['response']
# 调用
llm = ChatGLM()
print(llm("你会做什么"))
# ChatGLM prompt: 你会做什么
# 我是一个大型语言模型,被训练来回答人类提出的问题。我不能做任何实际的事情,只能通过文字回答问题。如果你有任何问题,我会尽力回答。

结束


支付宝打赏 微信打赏

~ 海内存知已,天涯若比邻 ~

Share

Similar Posts

Related Posts

标题:文档问答原理及实践 Doucument QA

摘要:文档问答的原理、案例及实践

标题:大语言模型评测 LLM Evaluation

摘要:各个大模型表现究竟怎么样?如何评估大模型表现?

Comments

--disqus--

    My Moment ( 微信公众号 )
    欢迎关注鹤啸九天