阅读完需:约 108 分钟
后续的测试都是 LangChain + ollama + chroma
来进行RAG构建
Ollama安装
ollama 可以在本地快速启动并运行大型语言模型,支持很多种大模型,具体的可以在上面查看:
https://github.com/ollama/ollama
这里先安装,使用的是docker安装的方式
https://hub.docker.com/r/ollama/ollama
如果服务器有GPU就采用GPU来启动和加速,没有就使用CPU来运行,目前是测试学习,可以仅仅使用CPU
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama
然后运行 ollama ,下载对应的模型,这里用的是 llama2-chinese:13b
这个模型是经过了中文的微调训练的,对于中文提问回答会比较友好
docker exec -it ollama ollama run llama2-chinese:13b
关于中文的 Llama 模型可以去国内的中文社区查看
https://github.com/LlamaFamily/Llama-Chinese
当前运行好大模型后就可以先在服务器上体验一下
在 ollama 上除了可以安装 LLM大模型,还可以安装向量嵌入模型,不过嵌入模型与正常模型运行上是有区别的,需要注意
docker exec -it ollama ollama pull nomic-embed-text
这样就算是安装好嵌入模型了,嵌入模型不能像大模型一样直接运行,需要去调用才行
Ollama在LangChain中的使用
在 LangChain 应用程序中使用 Ollama 聊天模型的典型基本示例
from langchain_community.llms import Ollama
#llm = Ollama(model="llama3")
llm = Ollama(base_url='http://10.4.1.15:11434', model="llama2-chinese:13b",temperature=0)
llm.invoke("Tell me a joke")
Chat 模型调用
from langchain_community.chat_models import ChatOllama
llm = ChatOllama(base_url='http://10.4.1.15:11434', model="llama2-chinese:13b")
ChatOllama
查看在 LangChain 应用程序中通过聊天模型使用 Ollama 的典型基本示例
# LangChain supports many other chat models. Here, we're using Ollama
from langchain_community.chat_models import ChatOllama
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
# supports many more optional parameters. Hover on your `ChatOllama(...)`
# class to view the latest available supported parameters
llm = ChatOllama(model="llama3")
prompt = ChatPromptTemplate.from_template("Tell me a short joke about {topic}")
# using LangChain Expressive Language chain syntax
# learn more about the LCEL on
# /docs/expression_language/why
chain = prompt | llm | StrOutputParser()
# for brevity, response is printed in terminal
# You can use LangServe to deploy your application for
# production
print(chain.invoke({"topic": "Space travel"}))
Ollama Functions
您可以采用与初始化标准 ChatOllama 实例类似的方式初始化 Ollama Functions
from langchain_experimental.llms.ollama_functions import OllamaFunctions
model = OllamaFunctions(model="llama3", format="json")
Ollama Embeddings
from langchain_community.embeddings import OllamaEmbeddings
embeddings = OllamaEmbeddings(base_url='http://10.4.3.41:11434', model="nomic-embed-text")
Ollama 案例
def modelResponse2(message):
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
# 高性能开放嵌入模型
# embeddings = OllamaEmbeddings(base_url='http://10.4.1.15:11434', model="mxbai-embed-large")
embeddings = OllamaEmbeddings(base_url='http://10.4.3.41:11434', model="nomic-embed-text")
persistent_client = chromadb.HttpClient(host='10.4.3.41', port=8000)
# 创建向量数据库
docsearch = Chroma(client=persistent_client, collection_name="Security8",embedding_function=embeddings)
prompt = ChatPromptTemplate.from_template("""仅根据所提供的上下文回答以下问题:<context>{context}</context>问题: {input}""")
# 指定 search_type="similarity" 表示使用相似度检索,,search_kwargs={"k": 6} 表示最多返回 6 个结果
retriever = docsearch.as_retriever(search_type="similarity",search_kwargs={'k': 10})
# 采用LCEL方式的流程RAG,基本材料数据-》用户输入-》组成模版-》调用大模型-》内容输出
rag_chain = (
{"input": RunnablePassthrough(), "context": retriever}
| prompt
| llm
| StrOutputParser()
)
res=rag_chain.invoke(message)
print(res)
Chroma向量数据库
Chroma 是一个新的 AI 原生开源数据库,非常轻量且易用。Chroma 是开源嵌入式数据库,它使知识、任务和技能可插入,从而轻松构建 LLM 应用程序。它可以在内存中运行(可在磁盘中),为数据库服务器提供服务(这和传统数据库类似)。
澄清几个关键概念:
- 向量数据库的意义是快速的检索;
- 向量数据库本身不生成向量,向量是由 Embedding 模型产生的;
- 向量数据库与传统的关系型数据库是互补的,不是替代关系,在实际应用中根据实际需求经常同时使用。
官方文档与GitHub地址
https://github.com/chroma-core/chroma
这里我们也使用docker来安装数据库,创建运行的时候进行了持久化的挂载
docker run -d -p 8000:8000 -v /home/xxxx/chroma_data:/chroma/chroma -e IS_PERSISTENT=TRUE -e ANONYMIZED_TELEMETRY=TRUE chromadb/chroma:latest
安装好后,需要一个界面来查看数据库的内容,这时候可以选择GitHub上的一个开源项目来连接数据库,这个是一个使用 Next.js 构建的 Chroma 嵌入数据库的管理 UI
https://github.com/flanker/chromadb-admin
Chroma 基本使用
# 版本0.5
pip install chromadb
# 升级
pip install chromadb -U
最基本的例子
import chromadb
# 连接数据库
chroma_client = chromadb.HttpClient(host='10.4.3.41', port=8000)
# 创建集合
collection = chroma_client.create_collection(name="my_collection1")
# 添加数据
collection.add(
documents=["This is a document about engineer", "This is a document about steak"],
metadatas=[{"source": "doc1"}, {"source": "doc2"}],
ids=["id1", "id2"]
)
# 查询数据
results = collection.query(
query_texts=["Which food is the best?"],
n_results=2
)
# 打印数据
print(results)
对应数据库界面
创建客户端
# 创建客户端
client = chromadb.Client() # 内存模式
client = chromadb.PersistentClient(path="./chromac") # 数据保存在磁盘
chroma_client = chromadb.HttpClient(host="localhost", port=8000) # docker客户端模式
操作集合
# 遍历集合
client.list_collections()
# 创建新集合
collection = client.create_collection("testname")
# 获取集合
collection = client.get_collection("testname")
# 创建或获取集合
collection = client.get_or_create_collection("testname")
# 删除集合
client.delete_collection("testname")
# 创建或获取集合
collection = client.get_or_create_collection(name="my_collection2")
collection = client.create_collection(name="my_collection2")
collection = client.create_collection(name="my_collection", embedding_function=emb_fn)
collection = client.get_collection(name="my_collection", embedding_function=emb_fn)
# 获取集合中最新的5条数据
collection.peek()
添加数据
# 添加数据
collection.add(
documents=["2022年2月2号,美国国防部宣布:将向欧洲增派部队,应对俄乌边境地区的紧张局势.", " 2月17号,乌克兰军方称:东部民间武装向政府军控制区发动炮击,而东部民间武装则指责乌政府军先动用了重型武器发动袭击,乌东地区紧张局势持续升级"],
metadatas=[{"source": "my_source"}, {"source": "my_source"}],
ids=["id1", "id2"]
)
# 如果 Chroma 收到一个文档列表,它会自动标记并使用集合的嵌入函数嵌入这些文档(如果在创建集合时没有提供嵌入函数,则使用默认值)。Chroma也会存储文档本身。如果文档过大,无法使用所选的嵌入函数嵌入,则会出现异常。
# 每个文档必须有一个唯一的相关ID。尝试.添加相同的ID两次将导致错误。可以为每个文档提供一个可选的元数据字典列表,以存储附加信息并进行过滤。
# 或者,您也可以直接提供文档相关嵌入的列表,Chroma将存储相关文档,而不会自行嵌入。
collection.add(
embeddings=[[1.2, 2.3, 4.5], [6.7, 8.2, 9.2]],
documents=["This is a document", "This is another document"],
metadatas=[{"source": "my_source"}, {"source": "my_source"}],
ids=["id1", "id2"]
)
更新数据
collection.update(
ids=["id1", "id2", "id3", ...],
embeddings=[[1.1, 2.3, 3.2], [4.5, 6.9, 4.4], [1.1, 2.3, 3.2], ...],
metadatas=[{"chapter": "3", "verse": "16"}, {"chapter": "3", "verse": "5"}, {"chapter": "29", "verse": "11"}, ...],
documents=["doc1", "doc2", "doc3", ...],
)
# 如果id在集合中未找到,则会记录错误并忽略更新。如果documents提供了但没有相应的embeddings,则将使用集合的嵌入函数重新计算嵌入。
# 如果提供的embeddings尺寸与集合的尺寸不一样,就会引发异常。
# Chroma 还支持一项upsert操作,该操作可以更新现有项目,如果它们尚不存在则添加它们。
collection.upsert(
ids=["id1", "id2", "id3", ...],
embeddings=[[1.1, 2.3, 3.2], [4.5, 6.9, 4.4], [1.1, 2.3, 3.2], ...],
metadatas=[{"chapter": "3", "verse": "16"}, {"chapter": "3", "verse": "5"}, {"chapter": "29", "verse": "11"}, ...],
documents=["doc1", "doc2", "doc3", ...],
)
# 如果id集合中不存在,则将根据 创建相应的项目add。具有现有 的项目id将根据 进行更新update。
查询数据
# 查询数据
results = collection.query(
query_embeddings=[[11.1, 12.1, 13.1],[1.1, 2.3, 3.2], ...],
n_results=10,
where={"metadata_field": "is_equal_to_this"},
where_document={"$contains":"search_string"}
)
或者:
results = collection.query(
query_texts=["俄乌战争发生在哪天?"],
n_results=2
)
删除数据
collection.delete(
ids=["id1", "id2", "id3",...],
where={"chapter": "20"}
)
Chroma Embedding
嵌入式是AI(人工智能)表示任何类型数据的原生方式,因此非常适合与各种AI(人工智能)工具和算法配合使用。它们可以表示文本、图像以及音频和视频。无论是本地使用安装的库,还是调用API,创建嵌入式都有很多选择。
Chroma为流行的嵌入式提供商提供了轻量级封装,使您可以轻松地在应用程序中使用它们。您可以在创建Chroma集合时设置一个嵌入函数,该函数将被自动使用,您也可以自己直接调用它们。
默认情况下,Chroma 使用Sentence Transformersall-MiniLM-L6-v2
模型来创建嵌入。此嵌入模型可以创建可用于各种任务的句子和文档嵌入。此嵌入函数在您的机器上本地运行,可能需要您下载模型文件(这将自动发生)。
from chromadb.utils import embedding_functions
default_ef = embedding_functions.DefaultEmbeddingFunction()
# 向量化速度很慢,不推荐使用
res = default_ef("hello world")
print(456, res)
自定义嵌入函数
可以创建自己的嵌入函数以与 Chroma 一起使用,它只需要实现协议EmbeddingFunction
。
from chromadb import Documents, EmbeddingFunction, Embeddings
class MyEmbeddingFunction(EmbeddingFunction):
def __call__(self, input: Documents) -> Embeddings:
# embed the documents somehow
return embeddings
# OpenAI 的嵌入 API
# Chroma 为 OpenAI 的嵌入 API 提供了一个方便的包装器。此嵌入功能在 OpenAI 的服务器上远程运行,并且需要 API 密钥。您可以通过在OpenAI注册一个帐户来获取 API 密钥。此嵌入功能依赖于openai python 包,您可以使用pip install openai.
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
api_key="YOUR_API_KEY",
model_name="text-embedding-ada-002"
)
LangChain中的使用
安装依赖包
pip install langchain-chroma
连接数据库,保存到磁盘
# 保存到磁盘
db2 = Chroma.from_documents(docs, embedding_function, persist_directory="./chroma_db")
docs = db2.similarity_search(query)
# 从磁盘加载
db3 = Chroma(persist_directory="./chroma_db", embedding_function=embedding_function)
docs = db3.similarity_search(query)
print(docs[0].page_content)
通过LangChain来访问底层数据库
import chromadb
persistent_client = chromadb.PersistentClient()
collection = persistent_client.get_or_create_collection("collection_name")
collection.add(ids=["1", "2", "3"], documents=["a", "b", "c"])
# 工具函数
langchain_chroma = Chroma(
client=persistent_client,
collection_name="collection_name",
embedding_function=embedding_function,
)
print("There are", langchain_chroma._collection.count(), "in the collection")
连接Docker的Chroma服务器
# create the chroma client
import uuid
import chromadb
from chromadb.config import Settings
# 连接数据地址
# client = chromadb.HttpClient(host='10.4.1.15', port=8000)
client = chromadb.HttpClient(settings=Settings(allow_reset=True))
client.reset() # resets the database
collection = client.create_collection("my_collection")
for doc in docs:
collection.add(
ids=[str(uuid.uuid1())], metadatas=doc.metadata, documents=doc.page_content
)
# 告诉LangChain使用我们的客户端和集合名称
db4 = Chroma(
client=client,
collection_name="my_collection",
embedding_function=embedding_function,
)
query = "What did the president say about Ketanji Brown Jackson"
docs = db4.similarity_search(query)
print(docs[0].page_content)
更新和删除数据
# 构建数据
ids = [str(i) for i in range(1, len(docs) + 1)]
# 添加数据
example_db = Chroma.from_documents(docs, embedding_function, ids=ids)
docs = example_db.similarity_search(query)
print(docs[0].metadata)
# 更新文档的元数据
docs[0].metadata = {
"source": "../../modules/state_of_the_union.txt",
"new_value": "hello world",
}
example_db.update_document(ids[0], docs[0])
print(example_db._collection.get(ids=[ids[0]]))
# 删除最后一个文档
print("count before", example_db._collection.count())
example_db._collection.delete(ids=[ids[-1]])
print("count after", example_db._collection.count())
案例解析PDF添加至Chroma数据库
import time
from langchain.document_loaders import PyPDFLoader, UnstructuredFileLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain_community.embeddings import OllamaEmbeddings
import chromadb
def create_base(file_path, kb_name):
"""
创建PDF文件向量库
:param kb_name:
:param file_path: 文件路径
:return:
"""
try:
print(f'file: {file_path}')
print("Start building vector database... %s", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
# loader = UnstructuredFileLoader(file_path, model="element")
loader = PyPDFLoader(file_path)
docs = loader.load()
# print(f'docs: {docs}')
# 文本分割
text_splitter = RecursiveCharacterTextSplitter(chunk_size=300,chunk_overlap=50)
docs = text_splitter.split_documents(docs)
# 数据库连接
persistent_client = chromadb.HttpClient(host='10.4.1.15', port=8000)
# 向量化
# embedding = OllamaEmbeddings(base_url='http://10.4.1.15:11434', model="mxbai-embed-large")
embedding = OllamaEmbeddings(base_url='http://10.4.1.15:11434', model="nomic-embed-text")
# embedding = OllamaEmbeddings(base_url='http://10.4.1.15:11434', model="llama2-chinese:13b",temperature=0)
# 构造向量库+conversation_id
# persist_directory = os.path.join(kb_name)
# 创建向量数据库
vectordb = Chroma.from_documents(
documents=docs,
embedding=embedding,
client=persistent_client, collection_name=kb_name
)
print("vectordb:", vectordb._collection.count())
print("Vector database building finished. %s", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
return {"status": 200, "message": "success"}
except Exception as e:
return {"status": 500, "message": str(e)}
if __name__ == '__main__':
res=create_base("/Users/xxxxx/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9/a7bb7938af84bed59855eb9bca4340ae/Message/MessageTemp/148259842ff9d654f0b6c93cbc4fb452/File/智能化管控平台建设.pdf","Security6")
print(res)
入库结果,入库的数据分割需要自己定义
LangChain
GitHub地址:
https://github.com/langchain-ai/langchain
测试时 LangChain的版本
Langchain 是一个开源框架,它允许开发人员将像 GPT-4 这样的大型语言模型与外部的计算和数据源结合起来。目前,它提供了 Python 和 JavaScript(确切地说是 TypeScript)的软件包。
Langchain 核心组件
上图展示了LangChain的工作原理,这是一个用于提升大型语言模型(LLMs)功能的框架。它通过三个核心组件实现增强:
- 首先是 Compents“组件”,为LLMs提供接口封装、模板提示和信息检索索引;
- 其次是 Chains“链”,它将不同的组件组合起来解决特定的任务,比如在大量文本中查找信息;
- 最后是 Agents“代理”,它们使得LLMs能够与外部环境进行交互,例如通过API请求执行操作。
LangChain 的这种结构设计使LLMs不仅能够处理文本,还能够在更广泛的应用环境中进行操作和响应,大大扩展了它们的应用范围和有效性。总体来说,LangChain是一个以 LLM (大语言模型)模型为核心的开发框架,LangChain的主要特性:
- 可以连接多种数据源,比如网页链接、本地PDF文件、向量数据库等
- 允许语言模型与其环境交互
- 封装了Model I/O(输入/输出)、Retrieval(检索器)、Memory(记忆)、Agents(决策和调度)等核心组件
- 可以使用链的方式组装这些组件,以便最好地完成特定用例。
拆分的细致一点可以分解为:
- 模型 I/O 封装
- LLMs:大语言模型
- Chat Models:一般基于 LLMs,但按对话结构重新封装
- PromptTemple:提示词模板
- OutputParser:解析输出
- 数据连接封装
- Document Loaders:各种格式文件的加载器
- Document Transformers:对文档的常用操作,如:split, filter, translate, extract metadata, etc
- Text Embedding Models:文本向量化表示,用于检索等操作
- Verctorstores: (面向检索的)向量的存储
- Retrievers: 向量的检索
- 记忆封装
- Memory:这里不是物理内存,从文本的角度,可以理解为“上文”、“历史记录”或者说“记忆力”的管理
- 架构封装
- Chain:实现一个功能或者一系列顺序功能组合
- Agent:根据用户输入,自动规划执行步骤,自动选择每步需要的工具,最终完成用户指定的功能
- Tools:调用外部功能的函数,例如:调 google 搜索、文件 I/O、Linux Shell 等等
- Toolkits:操作某软件的一组工具集,例如:操作 DB、操作 Gmail 等等
- 模型 Models 负责理解和生成语言,
- 提示Prompts 用于引导模型输出;
- 链条 Chains 代表将多个步骤串联起来完成复杂任务的过程;
- 代理 Agents 则用于让模型与外部环境互动,比如执行API调用。
- Embedding 嵌入与向量存储 VectorStore 是数据表示和检索的手段,为模型提供必要的语言理解基础。
如此,这整个系统构成了一个高度集成的框架,能够处理高级语言任务并在多种环境下进行动态交互。
LangChain 如何工作
LangChain 的工作流程可以概括为以下几个步骤:
- 提问:用户提出问题。
- 向语言模型查询:问题被转换成向量表示,用于在向量数据库中进行相似性搜索。
- 获取相关信息:从向量数据库中提取相关信息块,并将其输入给语言模型。
- 生成答案或执行操作:语言模型现在拥有了初始问题和相关信息,能够提供答案或执行操作。
展示了一个智能问答系统的工作流程,它从用户提出的问题(Question)开始,然后通过相似性搜索(Similarity Search)在一个大型数据库或向量空间中找到与之相关的信息。
得到的信息与原始问题结合后,由一个处理模型分析,以产生一个答案(Answer)。
这个答案接着被用来指导一个代理采取行动(Action),这个代理可能会执行一个API调用或与外部系统交互以完成任务。
整个流程反映了数据驱动的决策过程,其中包含了从信息检索到处理,再到最终行动的自动化步骤。
LangChain 应用场景
Langchain 的应用场景非常广泛,包括但不限于:
- 个人助手:可以帮助预订航班、转账、缴税等。
- 数据分析和数据科学:连接到公司的客户数据或市场数据,极大地促进数据分析的进展。
- 数据连接:Langchain 允许你将大型语言模型连接到你自己的数据源,比如数据库、PDF文件或其他文档。这意味着你可以使模型从你的私有数据中提取信息。
- 行动执行:不仅可以提取信息,Langchain 还可以帮助你根据这些信息执行特定操作,如发送邮件。无需硬编码:它提供了灵活的方式来动态生成查询,避免了硬编码的需求。
总之,Langchain 打开了一个充满可能性的新世界,让AI技术更加贴近我们的实际需求和数据,使得机器学习应用的发展更加多样化和个性化。
LangChain 应用架构和核心组件解析
LangChain 的魅力在于它能够将 LLM 模型、向量数据库、交互层 Prompt、外部知识、外部工具整合到一起,这就如同将各路英雄豪杰汇聚一堂,共襄盛举,进而可以自由构建 LLM 应用,犹如搭建一个自由王国,任由你驰骋驰骋。
LangChain 作为一个大语言模型开发框架,真的是 LLM 应用架构中的璀璨明珠,真的是一环扣一环的重要组成部分。
那到底什么是 LLM 应用架构呢?其实,通俗点说,就是指基于语言模型的应用程序设计和开发的架构,犹如一幅完整的蓝图,决定了大厦的结构和风格。
LangChain 的独特之处在于它能将 LLM 模型、向量数据库、交互层 Prompt、外部知识、外部工具,这些看似独立的元素,巧妙地整合在一起,就像把各种宝物聚集在一个宝箱中,进而可以自由构建 LLM 应用,犹如神笔马良般,描绘出一个自由而强大的应用天地。
LangChian 可以将 LLM 模型、向量数据库、交互层 Prompt、外部知识、外部工具整合到一起,进而可以自由构建 LLM 应用。
Models(模型)
下面我们以具体示例分别阐述下 Chat Modals, Embeddings, LLMs
Chat Models 聊天模型
LangChain 为使用聊天模型提供了一个标准接口。
聊天模型是语言模型的接口(包括入口和出口,类似函数的输入和输出)。虽然聊天模型在内部使用语言模型,但它们所提供的接口略有不同。
聊天模型 不是暴露一个 “输入文本,输出文本” 的 API,而是提供了一个以 “聊天消息” 作为输入和输出的接口。所以,聊天模型的接口是基于消息, 而不是原始文本。LangChain 目前支持的 Chat Modals 聊天模型的消息类型有
- AIMessage
- HumanMessage
- SystemMessage
- ChatMessage
其中 ChatMessage 接受一个任意的角色参数。
在构建对话系统时,特别是在使用大型语言模型(如 GPT-3.5)时,不同类型的消息(Messages)有助于更好地组织和管理对话内容。以下是一些常见的消息类型及其用途:
HumanMessage
HumanMessage 代表由用户或人类生成的消息。它是用户输入到对话系统中的内容。
用途:用于记录和传输由用户输入的消息内容
AIMessage
AIMessage 代表由人工智能或聊天机器人生成的消息。它通常是对用户输入的响应或系统触发的自动回复。
用途:用于记录和传输由 AI 生成的回复内容
SystemMessageSystemMessage
代表由系统生成的消息,通常用于传递系统状态、指令或元信息。这类消息不直接参与对话,但对控制对话流程和环境设置有重要作用。
用途:用于系统状态更新、环境设置或元信息传递
ChatMessageChatMessage
是一个通用类型,可以包含任何类型的消息内容。ChatMessage可以包含 AI 生成的消息、用户输入的消息以及系统消息。
用途:用于记录和传输对话中的任何消息,无论其来源。
Chat Models 聊天模型使用场景
-
AIMessage 和 HumanMessage:
- 在对话中区分用户输入和 AI 响应。
- 帮助追踪对话的来回交流,便于分析和改进对话系统的性能。
-
SystemMessage:
- 用于管理对话的状态和控制信息流。
- 可以包含会话开始、结束、重置等系统事件的信息。
-
ChatMessage:
- 提供一种统一的方式来存储和传输各种类型的消息。
- 适用于需要处理多种消息类型的复杂对话场景。
通过这些不同类型的消息,开发者可以更好地组织和管理对话系统的逻辑,使系统能够更有效地处理用户请求、生成适当的响应,并维护对话的上下文和状态。大多数情况下,您只需要处理 HumanMessage、AIMessage 和 SystemMessage。
Chat Models 聊天模型使用例子
# 导入的聊天模型,及消息类型
from langchain_community.chat_models import ChatOllama
from langchain.schema import (
AIMessage,
HumanMessage,
SystemMessage
)
# 初始化聊天对象
chat = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
# 向聊天模型发问
res = chat([HumanMessage(content="把这句话从英语翻译成法语:我喜欢编程")])
print(res)
打印结果
content="J'aime la programmation.\n" response_metadata={'model': 'llama2-chinese:13b', 'created_at': '2024-06-06T05:54:02.7599886Z', 'message': {'role': 'assistant', 'content': ''}, 'done_reason': 'stop', 'done': True, 'total_duration': 4315080448, 'load_duration': 4350541, 'prompt_eval_duration': 384755000, 'eval_count': 11, 'eval_duration': 3793820000} id='run-f9249765-8266-49c8-a637-03c4966c2685-0'
如果需要多个消息作为输入。
怎么办呢?这是一个系统和用户消息聊天模式的例子:
# 导入的聊天模型,及消息类型
from langchain_community.chat_models import ChatOllama
from langchain.schema import (
AIMessage,
HumanMessage,
SystemMessage
)
# 初始化聊天对象
chat = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
messages = [
SystemMessage(content="You are a helpful assistant that translates English to French."),
HumanMessage(content="I love programming.")
]
# 向聊天模型发问
res = chat(messages)
print(res)
当然也可以进行批量处理,批量输出。
# 导入的聊天模型,及消息类型
from langchain_community.chat_models import ChatOllama
from langchain.schema import (
AIMessage,
HumanMessage,
SystemMessage
)
# 初始化聊天对象
chat = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
batch_messages = [
[
SystemMessage(content="You are a helpful assistant that translates English to French."),
HumanMessage(content="I love programming.")
],
[
SystemMessage(content="You are a helpful assistant that translates English to French."),
HumanMessage(content="I love artificial intelligence.")
],
]
result = chat.generate(batch_messages)
print(result)
Chat Models 聊天模型的上下文缓存
如果用户问同一个问题,对结果进行了缓存,这样就可以减少接口的调用并且也能加快接口返回的速度。
LangChain 也很贴心的提供了缓存的功能。
并且提供了两种缓存方案,内存缓存方案和数据库缓存方案,当然支持的数据库缓存方案有很多种。
# 导入聊天模型,SQLiteCache模块
from langchain_community.chat_models import ChatOllama
import langchain
from langchain.cache import SQLiteCache
# 设置语言模型的缓存数据存储的地址
langchain.llm_cache = SQLiteCache(database_path=".langchain.db")
# 加载 llm 模型
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
# 第一次向模型提问
result = llm.predict('tell me a joke')
print(result)
# 第二次向模型提问同样的问题
result2 = llm.predict('tell me a joke')
print(result2)
流式响应在 LangChain 中的应用
另外聊天模式也提供了一种流媒体回应。这意味着,而不是等待整个响应返回,你就可以开始处理它尽快。
LangChain 提供了多种工具和组件,可以轻松集成和实现流式响应这一功能。
LangChain 流式响应的实现
- 设置 流式生成器: LangChain 提供了生成器函数的支持,可以逐步生成响应内容。这些生成器函数可以在生成部分响应时立即返回给用户,而不是等待完整响应生成完毕。
- 配置流式响应: 使用 LangChain 的流媒体功能时,需要配置相应的接口,使得系统可以处理和返回流式响应。
from langchain_openai import OpenAI
llm = OpenAI(model="gpt-3.5-turbo-instruct", temperature=0, max_tokens=512)
for chunk in llm.stream("Write me a song about sparkling water."):
print(chunk, end="", flush=True)
LangChain 流式响应优势
- 即时反馈:用户不需要等待完整的响应生成完毕即可看到部分内容,从而提高响应速度和用户体验。
- 资源优化:逐步生成和返回内容可以更好地利用计算和带宽资源,避免一次性处理大量数据带来的压力。
- 改进互动体验:在长时间生成内容的情况下,用户可以逐步看到生成的结果,减少等待的焦虑。
LangChain 流式响应总结
通过在 Langchain 中实现流式响应,开发者可以显著提升对话系统的用户体验和响应速度。流式响应不仅能提供即时反馈,还能更好地优化资源利用,是构建高效对话系统的重要技术手段。结合 Langchain 的强大功能,流式响应能够为用户带来更加流畅和自然的交互体验
嵌入 模型(embeddings model)
嵌入 模型更多的是用于文档、文本或者大量数据的总结、问答场景,一般是和向量库一起使用,实现向量匹配。
嵌入 模型其实就是把文本等内容转成多维数组,可以后续进行相似性的计算和检索。
什么是 嵌入模型(Embedding Model)?
嵌入模型是一种将离散的高维数据(如单词、句子、图片等)映射到连续的低维向量空间的技术。
这个低维向量空间中的点称为“嵌入”(embedding)。
嵌入模型的主要目的是捕捉输入数据中的语义或特征信息,使得相似的输入在嵌入空间中距离更近。
嵌入模型(Embedding Model)特点
- 低维表示:嵌入模型将高维、稀疏的离散数据转换为低维、密集的向量表示。
- 语义信息:嵌入向量捕捉了输入数据的语义或特征信息,向量之间的距离反映了输入数据之间的相似性。
- 效率高:嵌入模型通常计算高效,适合用于大规模数据的相似性搜索和分类任务。
常见嵌入模型
- Word2Vec:通过Skip-Gram或CBOW方法训练,生成单词的嵌入向量。
- GloVe:基于全局词共现矩阵训练,生成单词嵌入。
- FastText:扩展了Word2Vec,能够处理未见过的词汇。
嵌入模型(Embedding Model)应用场景
- 文本相似度计算:比较两个文本的嵌入向量,计算相似度。
- 信息检索:通过嵌入向量进行高效的相似性搜索。
- 分类和聚类:使用嵌入向量进行文本或图像的分类和聚类。
通过OpenAIEmbeddings 使用 嵌入模型(Embedding Model)
# 导入os, 设置环境变量,导入OpenAI的嵌入模型
import os
from langchain.embeddings.openai import OpenAIEmbeddings
os.environ["OPENAI_API_KEY"] = 'your apikey'
# 初始化嵌入模型
embeddings = OpenAIEmbeddings()
# 把文本通过嵌入模型向量化
res = embeddings.embed_query('hello world')
/*
[
-0.004845875, 0.004899438, -0.016358767, -0.024475135, -0.017341806,
0.012571548, -0.019156644, 0.009036391, -0.010227379, -0.026945334,
0.022861943, 0.010321903, -0.023479493, -0.0066544134, 0.007977734,
0.0026371893, 0.025206111, -0.012048521, 0.012943339, 0.013094575,
-0.010580265, -0.003509951, 0.004070787, 0.008639394, -0.020631202,
-0.0019203906, 0.012161949, -0.019194454, 0.030373365, -0.031028723,
0.0036170771, -0.007813894, -0.0060778237, -0.017820721, 0.0048647798,
-0.015640393, 0.001373733, -0.015552171, 0.019534737, -0.016169721,
0.007316074, 0.008273906, 0.011418369, -0.01390117, -0.033347685,
0.011248227, 0.0042503807, -0.012792102, -0.0014595914, 0.028356876,
0.025407761, 0.00076445413, -0.016308354, 0.017455231, -0.016396577,
0.008557475, -0.03312083, 0.031104341, 0.032389853, -0.02132437,
0.003324056, 0.0055610985, -0.0078012915, 0.006090427, 0.0062038545,
... 1466 more items
]
*/
LLMS 大语言模型
大型语言模型(Large Language Models, LLMs)定义大语言模型是一种深度学习模型,通常基于Transformer架构,具有数以亿计甚至数以百亿计的参数。
LLM通过在大规模文本语料库上进行训练,能够生成、理解和处理自然语言文本。
大型语言模型(Large Language Models, LLMs)特点
- 大规模:大语言模型拥有大量参数和复杂的网络结构,能够捕捉丰富的语言信息。
- 生成能力:能够生成高质量的自然语言文本,进行对话、写作等任务。
- 多任务处理:能够处理多种NLP任务,如翻译、总结、问答等。
常见大型语言模型(Large Language Models, LLMs)
- GPT-3:由OpenAI开发的生成型预训练模型,具有1750亿参数。
- BERT:由Google开发的双向编码器表示模型,适用于文本分类、命名实体识别等任务。
- T5:由Google开发的文本到文本转换模型,能够处理多种文本生成任务。
大型语言模型(Large Language Models, LLMs)应用场景
- 对话系统:用于构建智能对话助手,如ChatGPT。
- 文本生成:自动写作、内容创作、翻译等。
- 问答系统:回答用户的问题,提供信息检索和知识问答服务。
LangChain 和大型语言模型(Large Language Models, LLMs)是什么关系呢?
LangChain 是一个开源框架,旨在将大型语言模型(如 GPT-4)与外部数据源和计算资源结合起来,以实现复杂的 NLP 应用。
LangChain 提供了各种工具和组件,帮助开发者构建、部署和管理基于大型语言模型的应用程序。
大型语言模型(如 GPT-4、BERT)是基于深度学习的复杂模型,通常具有数以亿计甚至数以百亿计的参数。
这些模型通过在大规模文本语料库上进行训练,能够生成和理解自然语言文本,并执行多种 NLP 任务,如文本生成、翻译、摘要、问答等。
LangChain 和LLMS 大型语言模型的关系,大致如下:
- 集成和扩展: LangChain 提供了一个框架,使开发者可以轻松地集成和扩展大型语言模型。通过 LangChain,开发者可以将 LLM 的强大能力与其他工具和数据源结合,创建更复杂和强大的 NLP 应用。例如,可以将 LLM 与向量数据库、API 接口、外部计算资源等结合,增强其功能和应用场景。
- 组件化设计: LangChain 提供了各种组件,使开发者能够模块化地构建应用。这些组件包括模型管理、数据处理、任务调度、交互界面等。开发者可以根据需要选择和组合这些组件,以实现特定的应用需求。
- 增强的交互性: 通过 LangChain,开发者可以实现更复杂的用户交互。例如,可以设计多轮对话系统、复杂的问答系统,或者根据用户输入动态调用外部 API 进行信息查询和处理。这使得基于 LLM 的应用不仅限于单一的任务处理,而是能够实现更复杂的交互逻辑。
- 提升性能和效率: LangChain 通过优化模型调用、缓存机制、并行处理等技术手段,提升了 LLM 的性能和效率。尤其是在处理高并发请求或大规模数据时,LangChain 的设计能够显著降低延迟,提高响应速度。
所以,LLMS 是 LangChain 的核心,从官网可以看到 LangChain 继承了非常多的大语言模型。
流式响应:通常,当我们请求一个服务或者接口时,服务器会将所有数据一次性返回给我们,然后我们再进行处理。
但是,如果返回的数据量很大,那么我们需要等待很长时间才能开始处理数据。而流式响应则不同,它将数据分成多个小块,每次只返回一部分数据给我们。我们可以在接收到这部分数据之后就开始处理,而不需要等待所有数据都到达。
此外,所有的语言模型都实现了Runnable 接口,默认实现了invoke
,batch
,stream
,map
等方法, 提供了对调用、流式传输、批处理和映射请求的基本支持。
嵌入 模型 和 大模型的区别
那么,前面说的嵌入 模型 和 大模型的区别是什么呢?
嵌入模型(Embedding Model)和大语言模型(Large Language Model, LLM)是自然语言处理(NLP)领域中的两类重要模型,它们在设计目的、功能和应用场景上有明显的区别。
以下是对嵌入模型和大语言模型的详细介绍和比较。
特性 | 嵌入模型 | 大语言模型 |
目标 | 将离散数据映射到低维向量空间 | 生成和理解自然语言文本 |
表示形式 | 低维向量 | 大规模参数模型 |
复杂度 | 较低 | 较高 |
生成能力 | 无 | 有 |
应用场景 | 相似度计算、分类、信息检索 | 对话系统、文本生成、问答系统 |
训练数据 | 需要大量标注数据 | 需要大规模文本语料 |
嵌入模型和大语言模型各有其独特的优势和适用场景。
嵌入模型擅长于将高维数据转换为低维向量表示,适用于相似度计算和分类任务。
而大语言模型通过复杂的网络结构和大量参数,具备强大的自然语言生成和理解能力,适用于对话系统、文本生成和问答系统等复杂的NLP任务。
两者的结合和应用,能够为自然语言处理领域提供更加全面和强大的解决方案。
嵌入 模型相比大语言模型的 fine-tuning 最大的优势就是,不用进行训练,并且可以实时添加新的内容,而不用加一次新的内容就训练一次,并且各方面成本要比 fine-tuning 低很多。
Basic Language Model基础语言模型有哪些?
基础语言模型是指只在大规模文本语料中进行了预训练的模型,未经过指令和下游任务微调、以及人类反馈等任何对齐优化。
- 当前绝大部分的大语言模型都是 Decoder-only 的模型结构,;
- 大部分大语言模型都不开源,而 OPT、BLOOM、LLaMA 三个模型是主要面向开源促进研究和应用的,中文开源可用的是 GLM,后续很多工作都是在这些开源的基础模型上进行微调优化的。
Prompts(提示词)
前面说明了 嵌入 模型相比大语言模型 的区别。
再看看 聊天模型 相比 大语言模型 的区别。
聊天模型是 大语言模型 的一种变体。
聊天模型在底层使用大语言模型 ,或者说, 聊天模型是大语言模型 的应用层接口。
前面介绍了 LangChain 目前支持的 Chat Modals 聊天模型的4种 消息类型除了使用 4种 消息类型 直接写 聊天模型 之外, LangChain 提供了 编写 chat 模型的新方式,就是通过提示词(prompt) 间接实现 chat Modals 。
什么是 提示词(prompt) ?
一个 提示词(prompt) 指的是输入模型的内容。
这个输入通常由多个组件构成。LangChain 提供了多个类和函数,使构建和处理提示变得简单。
- 提示词模板(Prompt templates): 为模型输入添加参数
- 示例选择器(Example selectors): 动态选择在提示中包含的示例
Prompt Templates
LangChain 提供了 PromptTemplates,允许你可以根据用户输入动态地更改提示,如果你有编程基础,这应该对你来说很简单。
什么是提示词模板?提示词模板是一种预定义的文本结构,其中包含变量和固定文本部分,用于引导语言模型生成特定类型的输出。这些模板可以帮助模型更准确地理解上下文,并生成符合预期的响应。提示词模板的核心组件
- 固定文本部分:这是提示词模板中的静态部分,用于提供上下文或引导语言模型的生成方向。
- 变量部分:这是提示词模板中的动态部分,可以根据具体输入进行替换。这些变量通常用占位符表示。
当用户需要输入多个类似的 prompt 时,生成一个 prompt 模板是一个很好的解决方案,可以节省用户的时间和精力。
如何创建提示模板?可以使用 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."
# An example prompt with one input variable
one_input_prompt = PromptTemplate(input_variables=["adjective"], template="Tell me a {adjective} joke.")
one_input_prompt.format(adjective="funny")
# -> "Tell me a funny joke."
# 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."
如果 不想手动指定 input_variables
,也可以使用 from_template
类方法创建 PromptTemplate
。LangChain
将根据传递的 template
自动推断 input_variables
。
from langchain.prompts import PromptTemplate
template = PromptTemplate.from_template("给我讲个关于{subject}的笑话")
print("===Template===")
print(template)
print("===Prompt===")
print(template.format(subject='小明'))
您可以创建自定义的提示模板,以任何您想要的方式格式化提示。
Message Prompts 消息提示
LangChain 提供不同类型的MessagePromptTemplate
。最常用的是AIMessagePromptTemplate
、SystemMessagePromptTemplate
和HumanMessagePromptTemplate
,分别创建 AI 消息、系统消息和人类消息。
当聊天模型支持以任意角色接收聊天消息时,可以使用ChatMessagePromptTemplate
,它允许用户指定角色名称。
from langchain_core.prompts import ChatMessagePromptTemplate
prompt = "May the {subject} be with you"
chat_message_prompt = ChatMessagePromptTemplate.from_template(
role="Jedi", template=prompt
)
chat_message_prompt.format(subject="force")
ChatPromptTemplate 用模板表示的对话上下文
每条聊天消息都与内容以及一个名为的附加参数相关联role
。例如,在 OpenAI Chat Completions API中,聊天消息可以与 AI 助手、人类或系统角色相关联。
from langchain.prompts import (
ChatPromptTemplate,
HumanMessagePromptTemplate,
SystemMessagePromptTemplate,
)
from langchain_openai import ChatOpenAI
template = ChatPromptTemplate.from_messages(
[
SystemMessagePromptTemplate.from_template(
"你是{product}的客服助手。你的名字叫{name}"),
HumanMessagePromptTemplate.from_template("{query}"),
]
)
llm = ChatOpenAI()
prompt = template.format_messages(
product="卧龙",
name="凤雏",
query="你是谁"
)
ret = llm.invoke(prompt)
print(ret.content)
MessagesPlaceholder 把多轮对话变成模板
LangChain 还提供MessagesPlaceholder
,让您可以完全控制在格式化期间要呈现哪些消息。当您不确定应该为消息提示模板使用什么角色或希望在格式化期间插入消息列表时,这会很有用。
from langchain.prompts import (
ChatPromptTemplate,
HumanMessagePromptTemplate,
MessagesPlaceholder,
)
human_prompt = "Translate your answer to {language}."
human_message_template = HumanMessagePromptTemplate.from_template(human_prompt)
chat_prompt = ChatPromptTemplate.from_messages(
# variable_name 是 message placeholder 在模板中的变量名
# 用于在赋值时使用
[MessagesPlaceholder(variable_name="conversation"), human_message_template]
)
这段代码定义了模版,需要干什么事情
from langchain_core.messages import AIMessage, HumanMessage
human_message = HumanMessage(content="Who is Elon Musk?")
ai_message = AIMessage(
content="Elon Musk is a billionaire entrepreneur, inventor, and industrial designer"
)
messages = chat_prompt.format_prompt(
# 对 "conversation" 和 "language" 赋值
conversation=[human_message, ai_message], language="日文"
)
print(messages.to_messages())
[HumanMessage(content=’Who is Elon Musk?’), AIMessage(content=’Elon Musk is a billionaire entrepreneur, inventor, and industrial designer’), HumanMessage(content=’Translate your answer to 日文.’)]
这里将定义的模版代入,然后进行对话
result = llm.invoke(messages)
print(result.content)
イーロン・マスクは億万長者の起業家、発明家、産業デザイナーです。
从文件加载 Prompt 模板
from langchain.prompts import PromptTemplate
template = PromptTemplate.from_file("example_prompt_template.txt")
print("===Template===")
print(template)
print("===Prompt===")
print(template.format(topic='黑色幽默'))
---------------------------
===Template===
input_variables=['topic'] template='举一个关于{topic}的例子'
===Prompt===
举一个关于黑色幽默的例子
PipelinePrompt 提示词模版组合
可以通过 PipelinePrompt将多个PromptTemplate提示模版进行组合,组合的优点是可以很方便的进行复用。
比如常见的系统角色提示词,一般都遵循以下结构:{introduction} {example} {start},比如一个【名人采访】角色的提示词:
LangChain 包含一个抽象PipelinePromptTemplate
,当您想要重用提示的部分内容时,它会很有用。PipelinePrompt 由两个主要部分组成:
- 最终提示:返回的最终提示
- 管道提示:一个元组列表,由字符串名称和提示模板组成。每个提示模板将被格式化,然后作为同名变量传递给未来的提示模板。
from langchain_core.prompts.pipeline import PipelinePromptTemplate
from langchain_core.prompts.prompt import PromptTemplate
full_template = """{introduction}
{example}
{start}"""
full_prompt = PromptTemplate.from_template(full_template)
introduction_template = """You are impersonating {person}."""
introduction_prompt = PromptTemplate.from_template(introduction_template)
example_template = """Here's an example of an interaction:
Q: {example_q}
A: {example_a}"""
example_prompt = PromptTemplate.from_template(example_template)
start_template = """Now, do this for real!
Q: {input}
A:"""
start_prompt = PromptTemplate.from_template(start_template)
input_prompts = [
("introduction", introduction_prompt),
("example", example_prompt),
("start", start_prompt),
]
pipeline_prompt = PipelinePromptTemplate(
final_prompt=full_prompt, pipeline_prompts=input_prompts
)
print(
pipeline_prompt.format(
person="Elon Musk",
example_q="What's your favorite car?",
example_a="Tesla",
input="What's your favorite social media site?",
)
)
这里的复用应该指的是可以复用同名变量
提示词模板(Prompt Template)在自然语言处理和生成任务中,是设计和优化模型输入的一种方法。
提示词模板可以帮助大型语言模型(LLM)更好地理解和生成目标内容。以下是提示词模板的介绍及其在不同场景中的应用。
Few-shot example(小样本提示)
Few-shot examples 是一组可用于帮助语言模型生成更好响应的示例。
要生成具有 few-shot examples 的 prompt,可以使用 FewShotPromptTemplate。
该类接受一个 PromptTemplate 和一组 few-shot examples。
然后,它使用这些 few-shot examples 格式化 prompt 模板。看一个例子,需求是根据用户输入,让模型返回对应的反义词,
我们要通过示例来告诉模型什么是反义词, 这就是 few-shot examples(小样本提示)。
import os
os.environ["OPENAI_API_KEY"] = 'your apikey'
from langchain import PromptTemplate, FewShotPromptTemplate
from langchain.llms import OpenAI
examples = [
{"word": "黑", "antonym": "白"},
{"word": "伤心", "antonym": "开心"},
]
example_template = """
单词: {word}
反义词: {antonym}\\n
"""
# 创建提示词模版
example_prompt = PromptTemplate(
input_variables=["word", "antonym"],
template=example_template,
)
# 创建小样本提示词模版
few_shot_prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt,
prefix="给出每个单词的反义词",
suffix="单词: {input}\\n反义词:",
input_variables=["input"],
example_separator="\\n",
)
# 格式化小样本提示词
prompt_text = few_shot_prompt.format(input="粗")
# 调用OpenAI
llm = OpenAI(temperature=0.9)
print(llm(prompt_text))
Example Selector (示例选择器)
如果您有大量示例,您可能需要选择要包含在提示中的示例。示例选择器是负责执行此操作的类。
如果你有大量的示例,则可以使用 ExampleSelector 来选择最有信息量的一些示例,以帮助你生成更可能产生良好响应的提示。
为了大模型能够给出相对精准的输出内容,通常会在prompt中提供一些示例描述,如果包含大量示例会浪费token数量,甚至可能会超过最大token限制。
为此,LangChain提供了示例选择器,可以从用户提供的大量示例中,选择最合适的部分作为最终的prompt。
通常有2种方式:按长度选择和按相似度选择。
- 按长度选择:对于较长的输入,它将选择较少的示例来;而对于较短的输入,它将选择更多的示例。
- 按相似度选择:查找与输入具有最大余弦相似度的嵌入示例
接下来,将使用 LengthBasedExampleSelector
,根据输入的长度选择示例。当你担心构造的提示将超过上下文窗口的长度时,此方法非常有用。对于较长的输入,它会选择包含较少示例的提示,而对于较短的输入,它会选择包含更多示例。
import os
os.environ["OPENAI_API_KEY"] = 'your apikey'
from langchain.prompts import PromptTemplate, FewShotPromptTemplate
from langchain.prompts.example_selector import LengthBasedExampleSelector
# These are a lot of examples of a pretend task of creating antonyms.
examples = [
{"word": "happy", "antonym": "sad"},
{"word": "tall", "antonym": "short"},
{"word": "energetic", "antonym": "lethargic"},
{"word": "sunny", "antonym": "gloomy"},
{"word": "windy", "antonym": "calm"},
]
# 例子格式化模版
example_formatter_template = """
Word: {word}
Antonym: {antonym}\n
"""
example_prompt = PromptTemplate(
input_variables=["word", "antonym"],
template=example_formatter_template,
)
# 使用 LengthBasedExampleSelector来选择例子
example_selector = LengthBasedExampleSelector(
examples=examples,
example_prompt=example_prompt,
# 最大长度
max_length=25,
)
# 使用'example_selector'创建小样本提示词模版
dynamic_prompt = FewShotPromptTemplate(
example_selector=example_selector,
example_prompt=example_prompt,
prefix="Give the antonym of every input",
suffix="Word: {input}\nAntonym:",
input_variables=["input"],
example_separator="\n\n",
)
longString = "big and huge and massive and large and gigantic and tall and much much much much much bigger than everything else"
print(dynamic_prompt.format(input=longString))
聊天提示模板
前面介绍了 聊天模型 四种 消息。 LangChain 目前支持的 Chat Modals 聊天模型的 消息类型有
- AIMessage
- HumanMessage
- SystemMessage
- hatMessage
其中 ChatMessage 接受一个任意的角色参数。这些聊天消息与原始字符串不同,因为每个消息都与一个 role
相关联。
例如,在 OpenAI 的 聊天补全 API 中,一个聊天消息可以与 AI、人类或系统角色相关联。模型应更密切地遵循系统聊天消息的指令。
LangChain 提供了几个聊天 消息 提示模板,以便更轻松地构建和处理提示。在查询聊天模型时,建议您使用这些与聊天相关的提示模板,以充分发挥底层聊天模型的潜力。
from langchain.prompts import (
ChatPromptTemplate,
PromptTemplate,
SystemMessagePromptTemplate,
AIMessagePromptTemplate,
HumanMessagePromptTemplate,
)
from langchain.schema import (
AIMessage,
HumanMessage,
SystemMessage
)
要创建与角色相关联的消息模板,可以使用 MessagePromptTemplate
。
为了方便起见,模板上暴露了 from_template
方法。
如果您要使用此模板,它将如下所示:
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)
如果您想更直接地构建 MessagePromptTemplate
,您可以在外部创建一个 PromptTemplate,然后将其传递进去,例如:
prompt=PromptTemplate(
template="You are a helpful assistant that translates {input_language} to {output_language}.",
input_variables=["input_language", "output_language"],
)
system_message_prompt_2 = SystemMessagePromptTemplate(prompt=prompt)
assert system_message_prompt == system_message_prompt_2
之后,您可以从一个或多个 MessagePromptTemplates
构建一个 ChatPromptTemplate
。
可以使用 ChatPromptTemplate
的 format_prompt
方法 – 这将返回一个 PromptValue
,您可以将其转换为字符串或 Message 对象,具体取决于您是否希望将格式化的值用作 llm 或 chat 模型的输入。
chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])
# get a chat completion from the formatted messages
chat_prompt.format_prompt(input_language="English", output_language="French", text="I love programming.").to_messages()
Indexes( RAG 索引)
原始的大语言模型存在幻觉等问题,一些LLM应用通常需要特定的外部数据(外挂知识库)做增强,这些数据不属于模型训练集的一部分。
可以通过检索增强生成(RAG)的方式,检索外部数据(外挂知识库),然后在执行生成步骤时,将其传递给 LLM。
索引是指对外挂知识库文档进行索引的方法,以便 LLM 能够更好的与之交互。
LangChain 提供了 RAG 应用程序的所有构建模块,包含以下几个关键模块:
- Document Loaders(文档加载器)
- Text Splitters(文本拆分器)
- VectorStores(向量存储器)
- Retrievers(检索器)
Document Loaders
Document loaders可以从各种数据源加载文档。
Document loaders将特定格式的数据,转换为文本。
如 CSV、File Directory、HTML、JSON、Markdown、PDF等。
LangChain 提供了许多不同的文档加载器以及与对应的第三方集成工具。
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("llama2.pdf")
pages = loader.load_and_split()
print(pages[0].page_content)
TextSplitter
加载文档后,通常需要进行数据处理,比如:将长文档分割成更小的块、过滤不需要的HTML标签以及结构化处理等。
LangChain提供了许多内置的文档转换器,可以轻松地拆分、组合、过滤和以其他方式操作文档。
其中:
-
CharacterTextSplitter
它按照指定的分隔符(默认“\n\n”)进行分割,并且考虑文本片段的最大长度。当处理大量任意文档集合时,简单的文本分割可能会出现重叠文本的文档,CharacterTextSplitter可以用元数据标记文档,从而解决矛盾来源的信息等问题。 -
RecursiveCharacterTextSplitter
除了可以按指定分隔符进行分割外,还支持根据特定于语言的语法分割文本,比如:JavaScript、Python、Solidity 和 Rust 等流行语言,以及 Latex、HTML 和 Markdown。 - 当提取 HTML 文档以供以后检索时,我们通常只对网页的实际内容而不是语义感兴趣。
HtmlToTextTransformer
和MozillaReadabilityTransformer
都可以从文档中剥离HTML标签,从而使检索更加有效 -
MetadataTagger
转换器可以自动从文档中提取元数据,以便以后进行更有针对性的相似性搜索。
CharacterTextSplitter
示例
由于模型对输入的字符长度有限制,我们在碰到很长的文本时,需要把文本分割成多个小的文本片段。
文本分割最简单的方式是按照字符长度进行分割,但是这会带来很多问题,比如说如果文本是一段代码,一个函数被分割到两段之后就成了没有意义的字符,所以整体的原则是把语义相关的文本片段放在一起。
这是最简单的方法。该方法基于字符进行拆分(默认为“\n\n”),并根据字符数测量块长度。
- 文本的拆分方式:按单个字符。
- 块大小的测量方式:按字符数。
from langchain.text_splitter import CharacterTextSplitter
# 初始字符串
state_of_the_union = "..."
text_splitter = CharacterTextSplitter(
separator = "\\n\\n",
chunk_size = 1000,
chunk_overlap = 200,
length_function = len,
)
texts = text_splitter.create_documents([state_of_the_union])
RecursiveCharacterTextSplitter
示例
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=200,
chunk_overlap=100,
length_function=len,
add_start_index=True,
)
paragraphs = text_splitter.create_documents([pages[0].page_content])
for para in paragraphs:
print(para.page_content)
print('-------')
LangChain 多个高级文本分割器,如下:
Embedding Models(嵌入模型)
嵌入模型(Text embedding models)是,一个用于创建文本数据的数值表示的模型。
文本要进行相似查询和检索,需要用到 嵌入模型(Text embedding models)。嵌入模型(Text embedding models)可以将文本转换为向量表示,这种向量表示 就可以存储到向量数据库,也可以从向量库中进行相似查询。
嵌入模型其实就是把文本等内容转成多维数组,可以后续进行相似性的计算和检索。
嵌入模型更多的是用于文档、文本或者大量数据的总结、问答场景,一般是和向量库一起使用,而向量库用于实现向量的存储和匹配。
一个OpenAI的嵌入示例:通常要结合文档(Document)和向量存储(Vector stores)一起使用。
from langchain_openai import OpenAIEmbeddings
embeddings_model = OpenAIEmbeddings(api_key="...")
embeddings = embeddings_model.embed_documents(
[
"Hi there!",
"Oh, hello!",
"What's your name?",
"My friends call me World",
"Hello World!"
]
)
len(embeddings), len(embeddings[0])
embedded_query = embeddings_model.embed_query("What was the name mentioned in the conversation?")
embedded_query[:5]
本地嵌入模型基于Ollama部署
from langchain_community.chat_models import ChatOllama
from langchain_community.embeddings import OllamaEmbeddings
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
# 高性能开放嵌入模型
# embeddings = OllamaEmbeddings(base_url='http://10.4.1.15:11434', model="mxbai-embed-large")
embeddings = OllamaEmbeddings(base_url='http://10.4.3.41:11434', model="nomic-embed-text")
VectorStores(向量库)
存储和搜索非结构化数据的最常见方法之一是嵌入数据并存储生成的嵌入向量,然后在查询时嵌入非结构化查询并检索与嵌入查询“最相似”的嵌入向量。向量存储负责存储嵌入数据并为您执行向量搜索。
存储提取的文本向量,包括 Faiss、Milvus、Pinecone、Chroma 等。
如下是 LangChain 集成的向量数据库。
openAI的向量检索例子
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_openai import ChatOpenAI
from langchain_community.document_loaders import PyPDFLoader
# 加载文档
loader = PyPDFLoader("llama2.pdf")
pages = loader.load_and_split()
# 文档切分
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=200,
chunk_overlap=100,
length_function=len,
add_start_index=True,
)
texts = text_splitter.create_documents(
[page.page_content for page in pages[:4]]
)
# 灌库
embeddings = OpenAIEmbeddings()
db = Chroma.from_documents(texts, embeddings)
# 检索 top-1 结果
retriever = db.as_retriever(search_kwargs={"k": 1})
docs = retriever.get_relevant_documents("llama2 chat有多少参数")
print(docs[0].page_content)
本地嵌入模型基于Ollama部署与Chroma的例子
def modelResponse2(message):
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
# 高性能开放嵌入模型
# embeddings = OllamaEmbeddings(base_url='http://10.4.1.15:11434', model="mxbai-embed-large")
embeddings = OllamaEmbeddings(base_url='http://10.4.3.41:11434', model="nomic-embed-text")
persistent_client = chromadb.HttpClient(host='10.4.3.41', port=8000)
# 创建向量数据库
docsearch = Chroma(client=persistent_client, collection_name="Security8",embedding_function=embeddings)
prompt = ChatPromptTemplate.from_template("""仅根据所提供的上下文回答以下问题:<context>{context}</context>问题: {input}""")
# 指定 search_type="similarity" 表示使用相似度检索,,search_kwargs={"k": 6} 表示最多返回 6 个结果
retriever = docsearch.as_retriever(search_type="similarity",search_kwargs={'k': 10})
# 采用LCEL方式的流程RAG,基本材料数据-》用户输入-》组成模版-》调用大模型-》内容输出
rag_chain = (
{"input": RunnablePassthrough(), "context": retriever}
| prompt
| llm
| StrOutputParser()
)
res=rag_chain.invoke(message)
print(res)
Retrievers(向量检索)
数据进入数据库,仍然需要检索,LangChain支持多种检索算法。
LangChain支持易于上手的基本方法——即简单语义搜索。检索器是一种便于模型查询的存储数据的方式,LangChain 约定检索器组件至少有一个方法 get_relevant_texts,这个方法接收查询字符串,返回一组文档。
除了简单语义搜索,LangChain还在此基础上添加了一系列算法以提高性能。这些包括:
- 父文档检索器(Parent Document Retriever):允许为每个父文档创建多个嵌入,从而允许查找较小的块但返回更大的上下文
- 自查询检索器(Self Query Retriever):用户问题通常包含对某些内容的引用,这些内容不仅是语义的,而且表达了一些可以最好地表示为元数据过滤器的逻辑。自查询允许从查询中存在的其他元数据过滤器解析出查询的语义部分。
- Ensemble Retriever:可以更容易的从多个不同的来源检索文档或使用多种不同的算法。
检索器是一个接口,它根据非结构化查询返回文档。
检索器接受字符串查询作为输入,并返回文档列表作为输出。
注意,检索器的接口并不是 向量,而是非结构化的文档, 所以,检索器底层并不限于向量检索, 也可以是普通的ES查询。
当然,向量存储可以用作检索器的核心存储,但也有其他类型的存储, 比如ES倒排索引。
检索器(Retriever)是一个接口:根据非结构化查询返回文档。
Retriever比Vector Store更通用,创建Vector Store后,将其用作检索器的方法非常简单:
retriever = vectorStore.asRetriever()
此外,LangChain还提供了他类型的检索器,比如:
-
ContextualCompressionRetriever
:用给定查询的上下文来压缩它们,以便只返回相关信息,而不是立即按原样返回检索到的文档,同时还可以减少token数量。 -
MultiQueryRetriever
:从不同角度为给定的用户输入查询生成多个查询。 -
ParentDocumentRetriever
:在检索过程中,它首先获取小块,然后查找这些块的父 ID,并返回那些较大的文档。 -
SelfQueryRetriever
:一种能够查询自身的检索器。 -
VespaRetriever
:从Vespa.ai数据存储中检索文档。
针对不同的需求场景,可能需要对应的合适的检索器。
Chains(链)
链允许我们将多个组件组合在一起以创建一个单一的、连贯的任务。
另外我们也可以通过将多个链组合在一起,或者将链与其他组件组合来构建更复杂的链。
“Chains”(链)概念用于描述一系列任务和操作的组合,通过这些任务和操作,LLM可以实现更复杂的功能和工作流程。
LangChain 框架就是一个典型的工具,旨在将多个任务串联起来,以便构建复杂且功能强大的应用程序。
LLM Chains 是一系列由多个步骤(或任务)组成的流程,这些步骤通常包括数据预处理、模型推理、后处理、外部 API 调用、数据库查询等。
通过将这些步骤串联起来,开发者可以创建复杂的工作流程,使 LLM 能够执行更高级和复杂的任务。
LLM Chains 的核心组件,大致如下:
- 任务(Tasks): 每个任务是链中的一个独立步骤,可以是数据预处理、模型推理、结果生成等。
- 连接器(Connectors): 用于将任务串联起来,使得每个任务的输出可以作为下一个任务的输入。
- 控制流(Control Flow): 决定任务的执行顺序和条件,包括分支、循环等控制结构。
LLM Chains 可以应用于多种复杂场景,如多步骤问答系统、数据分析、自动化流程、内容生成等。LLM Chains 的设计原则,具体如下:
- 模块化: 每个任务应该是独立的模块,便于复用和维护。
- 灵活性: 设计链时应考虑到不同应用场景的需求,确保可以灵活调整任务顺序和内容。
- 扩展性: 链应能够轻松扩展,以添加新的任务或修改现有任务。
- 可维护性: 通过清晰的接口和文档,确保链的维护和更新过程简便。
开发者通过LLM Chains 串联多个任务,开发者可以实现比单一步骤更强大的功能和工作流程。
LangChain 框架为 LLM Chains 提供了丰富的组件和工具,使得构建和管理这些复杂流程变得更加高效和灵活。
无论是在问答系统、数据分析、内容生成等应用场景中,LLM Chains 都能显著提升应用的智能性和实用性。
简单的链 LLMChain
例如,我们可以创建一个链,它接受用户输入,使用 PromptTemplate 对其进行格式化,然后将格式化的响应传递给 LLM。
LLMChain 是一个简单的链,它围绕语言模型添加了一些功能。
它在整个 LangChain 中广泛使用,包括在其他链和代理中。
LLMChain 接受一个提示模板,将其与用户输入进行格式化,并返回 LLM 的响应。
from langchain import PromptTemplate, OpenAI, LLMChain
prompt_template = "What is a good name for a company that makes {product}?"
llm = OpenAI(temperature=0)
llm_chain = LLMChain(
llm=llm,
prompt=PromptTemplate.from_template(prompt_template)
)
llm_chain("colorful socks")
使用ChainVS不使用Chain的代码对比
先看看:未使用Chain场景,代码的组织方式。当未使用Chain时,Model I/O的实现分为两个部分:
- 提示模板的构建
- 模型的调用
两部分独立的装配和处理, 参考代码如下
# 导入LangChain中的提示模板
from langchain_core.prompts import PromptTemplate
# 原始字符串模板
template = "猪八戒吃{fruit}?"
# 创建LangChain模板
prompt_temp = PromptTemplate.from_template(template)
# 根据模板创建提示
prompt = prompt_temp.format(fruit='人参果')
# 导入LangChain中的OpenAI模型接口
from langchain_openai import OpenAI
# 创建模型实例
model = OpenAI(temperature=0)
# 传入提示,调用模型返回结果
result = model.invoke(prompt)
print(result)
再看看:使用Chain场景,代码的组织方式。
from langchain.chains.llm import LLMChain
from langchain_core.prompts import PromptTemplate
from langchain_openai import OpenAI
# 原始字符串模板
template = "猪八戒吃{fruit}?"
# 创建模型实例
llm = OpenAI(temperature=0)
# 创建LLMChain
llm_chain = LLMChain(
llm=llm,
prompt=PromptTemplate.from_template(template))
# 调用LLMChain,返回结果
result = llm_chain.invoke({"fruit": "人参果"})
print(result)
当使用Chain链时,代码结构则更简洁。
LLMChain 的调用方法
1. 直接调用链对象
可以直接调用链对象,实际调用对象内部实现的__call__
方法。
在新、高版本中不推荐使用且将被弃用。
注意:当像函数一样调用一个对象时,它实际上会调用该对象内部实现的__call__
方法。
from langchain.chains.llm import LLMChain
from langchain_community.chat_models import ChatOllama
from langchain_core.prompts import PromptTemplate
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
template = "猪八戒吃{fruit}?"
# 创建LLMChain
llm_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(template))
# 调用LLMChain,返回结果
result = llm_chain({"role": "猪八戒", "fruit": "人参果"})
print(result)
2. 通过run方法
通过run方法,返回的是字符串而不是字典。
run 等价于直接调用_call_
函数。
from langchain.chains.llm import LLMChain
from langchain_community.chat_models import ChatOllama
from langchain_core.prompts import PromptTemplate
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
template = "猪八戒吃{fruit}?"
# 创建LLMChain
llm_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(template))
# 调用LLMChain,返回结果
result = llm_chain.run({"role": "猪八戒", "fruit": "人参果"})
print(result)
run在新、高版本中不推荐使用且将被弃用。
3. 通过invoke方法
通过invoke方法,在调用链的时候,传入一个字典参数。
在新、高版本中推荐使用。
from langchain.chains.llm import LLMChain
from langchain_community.chat_models import ChatOllama
from langchain_core.prompts import PromptTemplate
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
# 创建模型实例
template = PromptTemplate(
input_variables=["role", "fruit"],
template="{role}喜欢吃{fruit}?",
)
# 创建LLMChain
llm_chain = LLMChain(llm=llm, prompt=template)
# 调用LLMChain,返回结果
# 如果提示模板中包含多个变量,在调用链的时候,可以使用字典一次性输入它们。
result = llm_chain.invoke({"role": "猪八戒", "fruit": "人参果"})
print(result)
4. 通过predict方法
通过predict方法,将输入键指定为关键字参数
from langchain.chains.llm import LLMChain
from langchain_community.chat_models import ChatOllama
from langchain_core.prompts import PromptTemplate
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
# 创建模型实例
template = PromptTemplate(
input_variables=["role", "fruit"],
template="{role}喜欢吃{fruit}?",
)
# 创建LLMChain
llm_chain = LLMChain(llm=llm, prompt=template)
# 调用LLMChain,返回结果
result = llm_chain.predict(role="猪八戒", fruit="人参果")
print(result)
5. 通过apply方法
apply方法允许输入列表运行链,一次处理多个输入。
from langchain.chains.llm import LLMChain
from langchain_community.chat_models import ChatOllama
from langchain_core.prompts import PromptTemplate
# 创建模型实例
template = PromptTemplate(
input_variables=["role", "fruit"],
template="{role}喜欢吃{fruit}?",
)
# 创建LLM
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
# 创建LLMChain
llm_chain = LLMChain(llm=llm, prompt=template)
# 输入列表
input_list = [
{"role": "猪八戒", "fruit": "人参果"}, {"role": "孙悟空", "fruit": "仙桃"}
]
# 调用LLMChain,返回结果
result = llm_chain.apply(input_list)
print(result)
6. 通过generate方法
generate方法类似于apply,但它返回一个LLMResult对象,而不是字符串。
LLMResult通常包含了在模型生成文本过程中的一些相关信息,例如令牌数量、模型名称等。
from langchain.chains.llm import LLMChain
from langchain_community.chat_models import ChatOllama
from langchain_core.prompts import PromptTemplate
# 创建模型实例
template = PromptTemplate(
input_variables=["role", "fruit"],
template="{role}喜欢吃{fruit}?",
)
# 创建LLM
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
# 创建LLMChain
llm_chain = LLMChain(llm=llm, prompt=template)
input_list = [
{"role": "猪八戒", "fruit": "人参果"}, {"role": "孙悟空", "fruit": "仙桃"}
]
# 调用LLMChain,返回结果
result = llm_chain.generate(input_list)
print(result)
简单顺序链 SimpleSequentialChain
顺序链的最简单形式,其中每个步骤都有一个单一的输入/输出,并且一个步骤的输出是下一步的输入。
如下就是将两个 LLMChain 进行组合成顺序链进行调用的案例。
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain.chains import SimpleSequentialChain
from langchain_community.chat_models import ChatOllama
# 定义第一个chain
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
template = """You are a playwright. Given the title of play, it is your job to write a synopsis for that title.
Title: {title}
Playwright: This is a synopsis for the above play:"""
prompt_template = PromptTemplate(input_variables=["title"], template=template)
synopsis_chain = LLMChain(llm=llm, prompt=prompt_template)
# 定义第二个chain
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
template = """You are a play critic from the New York Times. Given the synopsis of play, it is your job to write a review for that play.
Play Synopsis:
{synopsis}
Review from a New York Times play critic of the above play:"""
prompt_template = PromptTemplate(input_variables=["synopsis"], template=template)
review_chain = LLMChain(llm=llm, prompt=prompt_template)
# 通过简单顺序链组合两个LLMChain
overall_chain = SimpleSequentialChain(chains=[synopsis_chain, review_chain], verbose=True)
# 执行顺序链
review = overall_chain.invoke("Tragedy at sunset on the beach")
print(review)
通用顺序链 SequentialChain
在调用语言模型之后,要对语言模型进行一系列的调用。当您希望将一个调用的输出作为另一个调用的输入时,这尤其有用。
相比 SimpleSequentialChain 只允许有单个输入输出,它是一种更通用的顺序链形式,允许多个输入/输出。
特别重要的是: 我们如何命名输入/输出变量名称。
在上面的示例中,我们不必考虑这一点,因为我们只是将一个链的输出直接作为输入传递给下一个链,但在这里我们确实需要担心这一点,因为我们有多个输入。
from langchain.chains import LLMChain
from langchain.chains.sequential import SequentialChain
from langchain.prompts import PromptTemplate
from langchain_community.chat_models import ChatOllama
# 这是一个 LLMChain,根据戏剧的标题和设定的时代,生成一个简介。
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
template = """You are a playwright. Given the title of play and the era it is set in, it is your job to write a synopsis for that title.
# 这里定义了两个输入变量title和era,并定义一个输出变量:synopsis
Title: {title}
Era: {era}
Playwright: This is a synopsis for the above play:"""
prompt_template = PromptTemplate(input_variables=["title", "era"], template=template)
synopsis_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="synopsis")
# 这是一个 LLMChain,根据剧情简介撰写一篇戏剧评论。
llm =ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
template = """You are a play critic from the New York Times. Given the synopsis of play, it is your job to write a review for that play.
# 定义了一个输入变量:synopsis,输出变量:review
Play Synopsis:
{synopsis}
Review from a New York Times play critic of the above play:"""
prompt_template = PromptTemplate(input_variables=["synopsis"], template=template)
review_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="review")
overall_chain = SequentialChain(
chains=[synopsis_chain, review_chain],
input_variables=["era", "title"],
# Here we return multiple variables
output_variables=["synopsis", "review"],
verbose=True)
res = overall_chain({"title":"Tragedy at sunset on the beach", "era": "Victorian England"})
print(res)
转换链TransformChain
转换链允许我们创建一个自定义的转换函数来处理输入,将处理后的结果用作下一个链的输入。
如下示例我们将创建一个转换函数,它接受超长文本,将文本过滤为仅前 3 段,然后将其传递到 LLMChain 中以总结这些内容。
from langchain.chains import TransformChain, LLMChain, SimpleSequentialChain
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
# 模拟超长文本
with open("../../state_of_the_union.txt") as f:
state_of_the_union = f.read()
# 定义转换方法,入参和出参都是字典,取前三段
def transform_func(inputs: dict) -> dict:
text = inputs["text"]
shortened_text = "\n\n".join(text.split("\n\n")[:3])
return {"output_text": shortened_text}
# 转换链:输入变量:text,输出变量:output_text
transform_chain = TransformChain(
input_variables=["text"], output_variables=["output_text"], transform=transform_func
)
# prompt模板描述
template = """Summarize this text:
{output_text}
Summary:"""
# prompt模板
prompt = PromptTemplate(input_variables=["output_text"], template=template)
# llm链
llm_chain = LLMChain(llm=OpenAI(), prompt=prompt)
# 使用顺序链
sequential_chain = SimpleSequentialChain(chains=[transform_chain, llm_chain])
# 开始执行
sequential_chain.run(state_of_the_union)
# 结果
"""
' The speaker addresses the nation, noting that while last year they were kept apart due to COVID-19, this year they are together again.
They are reminded that regardless of their political affiliations, they are all Americans.'
"""
LangChain表达式语言 (LCEL)
LangChain Expression Language(LCEL)是一种声明式语言,可轻松组合不同的调用顺序构成 Chain。LCEL 自创立之初就被设计为能够支持将原型投入生产环境,无需代码更改,从最简单的“提示+LLM”链到最复杂的链(已有用户成功在生产环境中运行包含数百个步骤的 LCEL Chain)。
LCEL 的一些亮点包括:
- 流支持:使用 LCEL 构建 Chain 时,你可以获得最佳的首个令牌时间(即从输出开始到首批输出生成的时间)。对于某些 Chain,这意味着可以直接从 LLM 流式传输令牌到流输出解析器,从而以与 LLM 提供商输出原始令牌相同的速率获得解析后的、增量的输出。
- 异步支持:任何使用 LCEL 构建的链条都可以通过同步 API(例如,在 Jupyter 笔记本中进行原型设计时)和异步 API(例如,在 LangServe 服务器中)调用。这使得相同的代码可用于原型设计和生产环境,具有出色的性能,并能够在同一服务器中处理多个并发请求。
- 优化的并行执行:当你的 LCEL 链条有可以并行执行的步骤时(例如,从多个检索器中获取文档),我们会自动执行,无论是在同步还是异步接口中,以实现最小的延迟。
- 重试和回退:为 LCEL 链的任何部分配置重试和回退。这是使链在规模上更可靠的绝佳方式。目前我们正在添加重试/回退的流媒体支持,因此你可以在不增加任何延迟成本的情况下获得增加的可靠性。
- 访问中间结果:对于更复杂的链条,访问在最终输出产生之前的中间步骤的结果通常非常有用。这可以用于让最终用户知道正在发生一些事情,甚至仅用于调试链条。你可以流式传输中间结果,并且在每个 LangServe 服务器上都可用。
- 输入和输出模式:输入和输出模式为每个 LCEL 链提供了从链的结构推断出的 Pydantic 和 JSONSchema 模式。这可以用于输入和输出的验证,是 LangServe 的一个组成部分。
- 无缝 LangSmith 跟踪集成:随着链条变得越来越复杂,理解每一步发生了什么变得越来越重要。通过 LCEL,所有步骤都自动记录到 LangSmith,以实现最大的可观察性和可调试性。
- 无缝 LangServe 部署集成:任何使用 LCEL 创建的链都可以轻松地使用 LangServe 进行部署。
LCEL 语法样例
from langchain_community.chat_models import ChatOllama
from langchain_core.prompts import PromptTemplate
# 原始字符串模板
template = "猪八戒吃{fruit}?"
prompt = PromptTemplate.from_template(template)
# 创建模型实例
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
# 创建Chain
chain = prompt | llm
# 调用Chain,返回结果
result = chain.invoke({"fruit": "人参果"})
print(result)
Chain 和 LangChain Expression Language (LCEL)
为了理解 LCEL 语法,让我们首先使用传统的 LangChain 语法构建一个简单的链。
# 导入所需的模块和类
from langchain.prompts import ChatPromptTemplate
from langchain.schema.output_parser import StrOutputParser
from langchain.chains import LLMChain
from langchain_community.chat_models import ChatOllama
# 创建聊天提示模板,指定要获取关于的主题
prompt = ChatPromptTemplate.from_template(
"给我一个关于{topic}的一句话介绍"
)
# 创建ChatOpenAI模型实例
model = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
# 创建输出解析器实例
output_parser = StrOutputParser()
# 创建LLMChain链,将聊天提示、模型和输出解析器组合在一起
chain = LLMChain(
prompt=prompt,
llm=model,
output_parser=output_parser
)
# 运行链,并指定主题为"大语言模型"
out = chain.run(topic="大语言模型")
print(out)
# -> 大语言模型是一种基于深度学习的人工智能技术,能够自动学习和生成自然语言文本,具有广泛的应用领域,如机器翻译、文本生成和对话系统等
这个链的目标是使用 Llama2 模型生成一个简短的关于指定主题的介绍。
我们通过设置温度参数为 0,确保模型生成的输出更加确定性,使得结果更加精确和可控。
而通过 LCEL 语法,我们使用管道操作符(|
)而不是 LLMChain
来创建我们的链。
# 使用 LangChain Expression Language(LCEL)创建链
lcel_chain = prompt | model | output_parser
# 运行链,并通过字典传递主题为"大语言模型"
out = lcel_chain.invoke({"topic": "大语言模型"})
print(out)
# -> 大语言模型是一种基于深度学习的人工智能技术,能够自动学习和生成自然语言文本,具有广泛的应用领域,如机器翻译、文本生成和对话系统等
这里的语法并不典型于Python,但只使用了原生Python。
|
操作符简单地将左侧的输出传递给右侧的函数。
管道运算符的工作原理
为了理解 LCEL 和管道运算符的工作原理,我们创建自己的管道兼容函数。
当 Python 解释器在两个对象之间看到 | 运算符(如 a | b)时,它会尝试将对象 a 传递给对象 b 的 or 方法。
这意味着这些模式是等价的:
# 对象方法
chain = a.__or__(b)
chain("一些输入")
# 管道方法
chain = a | b
chain("一些输入")
考虑到这一点,我们可以构建一个 Runnable
类,它接受一个函数并将其转换为可以使用管道运算符 |
与其他函数链接的函数。
class Runnable:
def __init__(self, func):
self.func = func
def __or__(self, other):
def chained_func(*args, **kwargs):
# 其他函数使用这个函数的结果
return other(self.func(*args, **kwargs))
return Runnable(chained_func)
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
让我们实现这个,取值 3,加上 5(得到 8),然后乘以 2,最后期望得到 16。
def add_five(x):
return x + 5
def multiply_by_two(x):
return x * 2
# 使用 Runnable 包装这些函数
add_five = Runnable(add_five)
multiply_by_two = Runnable(multiply_by_two)
# 使用对象方法运行它们
chain = add_five.__or__(multiply_by_two)
print(chain(3)) # (3 + 5) * 2 = 16
# -> 16
直接使用 __or__
我们会得到正确答案,让我们尝试使用管道操作符 |
将它们链接在一起:
# 将可运行的函数链接在一起
chain = add_five | multiply_by_two
# 调用链
print(chain(3)) # (3 + 5) * 2 = 16
# -> 16
无论使用哪种方法,我们都会得到相同的响应,这就是 LCEL 在链接组件时使用的管道逻辑。
RunnableLambda
是一个 LangChain 抽象,它允许我们将 Python 函数转换为与管道兼容的函数,类似于我们在之前介绍的 Runnable 类。
尝试一下我们之前的 add_five 和 multiply_by_two 函数。
from langchain_core.runnables import RunnableLambda
# 使用 RunnableLambda 包装这些函数
add_five = RunnableLambda(add_five)
multiply_by_two = RunnableLambda(multiply_by_two)
与之前的 Runnable
抽象类似,我们可以使用 |
运算符将 RunnableLambda
抽象连接在一起:
# 将可运行的函数链接在一起
chain = add_five | multiply_by_two
与我们的 Runnable
抽象不同,我们不能通过直接调用它来运行 RunnableLambda
链,而是必须调用 chain.invoke
:
# 调用链
print(chain.invoke(3))
# -> 16
以看到使用 RunnableLambda 获得了和 Runnable 类似的结果。
以上内容概述了 LangChain 表达语言(LCEL)的基础知识,通过 LCEL 我们可以轻松地构建链式结构。
LCEL 的优劣势多种多样。喜欢 LCEL 的人通常注重其极简的代码风格,以及对流式、并行操作和异步的支持,同时也看好 LCEL 与 LangChain 在组件链式连接方面的良好集成。
然而,有些人对 LCEL 持有不太喜欢的态度。这些人通常指出 LCEL 是对已经非常抽象的库再加一层抽象,语法令人困扰,违背了 Python 之禅,并且需要花费较多的时间来学习新的(或不太常见的)语法。
这两种观点都是有道理的,因为 LCEL 是一种极为不同的方法。
然而,由于 LCEL 具有快速开发的特性,目前在 LangChain 开源社区中被广泛使用。
对 LCEL 原理的简单了解将有助于读者在今后使用各种 LangChain 代码时更加得心应手
LangChain 预置链
LangChain中提供了很多种类型的预置链,目的是使各种各样的任务实现起来更加方便、规范。
LangChain支持两种类型的链:
- 使用LCEL构建的链,LangChain提供了一个更高级别的构造方法,实际上所有工作都是使用LCEL构建链。
- [遗留]通过从继承自遗留Chain类构建的链,这些链独立于LCEL而存在。
https://python.langchain.com/v0.1/docs/modules/chains/
LangChain Memory(记忆)
如果不具备这个能力,那么就如同下面的这个场景一样,每一次的新的对话,都是重新开启和前面的内容不会有任何的关联关系。
如果具备上下文管理能力,表示在多个轮次的对话中,能够自然而然的反应到前面对话提及的内容。
大多数大模型应用中都包含对话功能,而对话功能的基础就是参与者能够基于已经发生的对话和获取到的知识产生新的对话内容。
更复杂一点的场景中对话者甚至需要具有一个完整的对世界的认知,再根据对话中的信息对认知不断的进行迭代更新。
在LangChain中,这种能力被称为Memory。Memory 有考虑以下两个方面:
通过 Memory ,实现 上下文管理能力。
Token的消费控制 , 通过Memory ,减少Token使用, 降低成本。
通过 Memory ,可以对于历史的聊天记录进行记忆, 避免每次去调用大模型
上下文管理能力如何实现?
熟悉 openai 的都知道,openai 提供的聊天接口 api,本身是不具备“记忆的”能力。
如果想要使聊天具有记忆功能,则需要我们自行维护聊天记录,即每次把聊天记录发给 gpt。
具体过程如下
第一次发送:
import openai
openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello"},
]
)
第二次发送就要带上我们第一次的记录:
import openai
openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "Hello, how can I help you?"},
{"role": "user", "content": "who is more stylish Pikachu or Neo"},
]
)
那如果我们一直聊天下去,发送的内容也越来越多,那很可能就碰到 token 的限制。
很多人会发现,其实我们只保留最近几次的聊天记录就可以了。
没错,其实 LangChain 也是这样实现的,不过 LangChain 提供了更多的方法。
langchain 提供了不同的 Memory 组件完成内容记忆,如下是目前提供的组件。
会话缓冲ConversationBufferMemory
ConversationBufferMemory
的主要作用在于实现基本的对话功能。基本没有什么使用上的限制。而且可以轻松的获取到对话的上下文。
该组件类似我们上面的描述,只不过它会将聊天内容记录在内存中,而不需要每次再手动拼接聊天记录。
ConversationBufferMemory
是LangChain中最基础的记忆组件。
ConversationBufferMemory
的工作原理非常简单: 将对话历史缓存到一个队列中,并提供接口获取历史对话。这种缓存机制实现了最基本的对话“记忆”功能。
当用户询问之前提到的问题时,ConversationBufferMemory
可以查找相关记忆,从而使机器人的回答更加连贯合理。
# 导入所需的库
from langchain.chains import ConversationChain
from langchain.chains.conversation.memory import ConversationBufferMemory
from langchain_community.chat_models import ChatOllama
# 初始化大语言模型
# 大模型定义
api_key = ""
api_url = ""
modal= "baichuan"
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
# 初始化对话链
conversation = ConversationChain(
llm=llm,
memory=ConversationBufferMemory()
)
# 第一天的对话
# 回合1
conversation("我姐姐明天要过生日,我需要一束生日花束。")
print("第一次对话后的记忆:", conversation.memory.buffer)
# 回合2
conversation("她喜欢粉色玫瑰,颜色是粉色的。")
print("第二次对话后的记忆:", conversation.memory.buffer)
# 回合3 (第二天的对话)
conversation("我又来了,还记得我昨天为什么要来买花吗?")
print("/n第三次对话后时提示:/n",conversation.prompt.template)
print("/n第三次对话后的记忆:/n", conversation.memory.buffer)
如以上代码和执行结果所示, ConversationBufferMemory
会将所有的对话历史存储在buffer中
开发者可以通过’conversation.memory.buffer
’,访问最近的对话历史。
然后基于这些历史信息进行后续处理,从而实现机器人的“记忆”功能
这种Remember Everything的全历史记忆策略非常简单直接,但是同时也存在一些问题:
- 记忆容量有限,长对话下容易撑爆内存
- 对话噪声也全部记住,降低有效信息密度
所以这只是一个低级的记忆实现
我们还需要更智能的记忆机制,为了解决容量有限及,token耗费过高的问题,Langchain提供了时间窗口记忆组件。
时间窗口记忆ConversationBufferWindowMemory
相比较第一个记忆组件,该组件增加了一个窗口参数,会保存最近看 k 轮的聊天内容。
既然全历史记忆有容量限制问题,那么可以试试只记住部分重要的对话片段。
ConversationBufferWindowMemory
实现了基于时间窗口的记忆策略。
它与全历史缓存的差别在于,只维护一个滑动时间窗口,例如最近5轮对话。超出这个窗口的历史对话将被移出缓存。
# 导入所需的库
from langchain.chains import ConversationChain
from langchain.chains.conversation.memory import ConversationBufferWindowMemory
from langchain_community.chat_models import ChatOllama
# 初始化大语言模型
# 大模型定义
api_key = ""
api_url = ""
modal= "baichuan"
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
# 初始化对话链
conversation = ConversationChain(
llm=llm,
memory=ConversationBufferWindowMemory(k=1)
)
# 第一天的对话
# 回合1
result = conversation("我姐姐明天要过生日,我需要一束生日花束。")
print(result)
# 回合2
result = conversation("她喜欢粉色玫瑰,颜色是粉色的。")
print("\n第二次对话后的记忆:\n", conversation.memory.buffer)
print(result)
# 第二天的对话
# 回合3
result = conversation("我又来了,还记得我昨天为什么要来买花吗?")
print(result)
如以上代码所示,buffer中只保留了最近一次对话的记忆。
这种窗口机制实现了“遗忘”功能, 有效控制了记忆容量, 防止内存泄漏,
执行结果截图如下:
与此同时,通过窗口大小调整,开发者可以平衡记忆容量和内容质量:
- 窗口越大,记住的内容越多
- 窗口越小,记忆更加重点和精炼
但是, 时间窗口记忆ConversationBufferWindowMemory
,似乎一种粗暴的 截断式遗忘依然是一个相对简单的解决方案。
后续我们将探索有选择性地生成“记忆”,这才更符合人类智能的特点。
会话总结ConversationSummaryMemory
之前的两种记忆机制要么占用过多容量,要么丢失太多重要信息。我们需要一种折中方案——保留关键信 息,移除冗余Noise。
结合了上面两个思路,存储一个用户和机器人之间的聊天内容的摘要,并使用 token 长度来确定何时刷新交互。
这种类型的记忆会随时间推移创建对话摘要。这对于压缩随时间推移的对话信息非常有用。对话摘要记忆会随时间推移总结对话并将当前摘要存储在记忆中。
然后可以使用此记忆将迄今为止的对话摘要注入提示/链中。此记忆最适合较长的对话,因为将过去的消息历史记录逐字逐句地保留在提示中会占用太多标记。
ConversationSummary
系列组件通过生成语义摘要的方式实现这一目标。
# 导入所需的库
from langchain.chains import ConversationChain
from langchain.memory import ConversationSummaryMemory
from langchain_community.chat_models import ChatOllama
# 初始化大语言模型
# 大模型定义
api_key = ""
api_url = ""
modal= "baichuan"
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
# 初始化对话链
conversation = ConversationChain(
llm=llm,
memory=ConversationSummaryMemory(llm=llm)
)
# 第一天的对话
# 回合1
result = conversation("我姐姐明天要过生日,我需要一束生日花束。")
print(result)
# 回合2
result = conversation("她喜欢粉色玫瑰,颜色是粉色的。")
print("\n第二次对话后的记忆:\n", conversation.memory.buffer)
print(result)
# 第二天的对话
# 回合3
result = conversation("我又来了,还记得我昨天为什么要来买花吗?")
print(result)
其工作流程是:
- 对对话历史进行语义分析,提取关键词、实体等语义信息
- 基于这些语义信息,生成文本摘要
- 将生成的摘要作为记忆,而不是完整的对话历史 这种策略融合了记忆质量和容量的考量。摘要只保留了最核心的语义信息,有效减少了冗余,同时质量也 更高。
ConversationSummaryMemory
会在每轮对话后更新一次摘要。
而ConversationSummaryBufferMemory
支持配置生成摘要的间隔,从而进一步降低计算消耗。
会话摘要缓冲ConversationSummaryBufferMemory
它在内存中保留最近交互的缓冲区,但不是完全清除旧交互,而是将它们编译成摘要并同时使用。它使用标记长度而不是交互次数来确定何时清除交互。
ConversationSummaryBufferMemory
结合了前两个想法。
哪个两者:
-
ConversationBufferWindowMemory
时间窗口记忆 -
ConversationSummaryMemory
摘要记录
与先前的实现不同的是,它使用令牌长度而不是交互次数来确定何时清除 历史会话。
http://docs.autoinfra.cn/docs/modules/memory/types/summary_buffer
https://python.langchain.com/v0.1/docs/modules/memory/types/summary_buffer/
from langchain.memory import ConversationSummaryBufferMemory
from langchain.llms import OpenAI
llm = OpenAI()
memory = ConversationSummaryBufferMemory(
llm=llm, max_token_limit=40, return_messages=True
)
memory.save_context({"input": "嗨"}, {"output": "最近怎么样?"})
memory.save_context({"input": "没什么特别的,你呢?"}, {"output": "没什么特别的。"})
memory.load_memory_variables({})
输出结果
{'history': [SystemMessage(content='\nThe human and AI are engaging in
conversation. The human greets the AI and the AI asks how the human is doing.',
additional_kwargs={}),
HumanMessage(content='没什么特别的,你呢?', additional_kwargs={},
example=False),
AIMessage(content='没什么特别的。', additional_kwargs={}, example=False)]}
示例代码
from langchain.chains import ConversationChain
from langchain.memory import ConversationSummaryBufferMemory
from langchain_community.chat_models import ChatOllama
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
conversation_with_summary = ConversationChain(
llm=llm,
# We set a very low max_token_limit for the purposes of testing.
memory=ConversationSummaryBufferMemory(llm=llm, max_token_limit=250),
verbose=True,
)
conversation_with_summary.predict(input="嗨,最近怎么样?")
输出结果
> Entering new ConversationChain chain...
Prompt after formatting:
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.
Current conversation:
Human: 嗨,最近怎么样?
AI:
> Finished chain.
' 嗨!最近还不错,我在学习新的技能,并且正在尝试新的任务。我也在尝试改进我的语言处理能力,以便更好地与人交流。你呢?'
示例代码
conversation_with_summary.predict(input="只是在写一些文档!")
输出结果
Entering new ConversationChain chain...
Prompt after formatting:
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.
Current conversation:
Human: 嗨,最近怎么样?
AI: 嗨!最近还不错,我在学习新的技能,并且正在尝试新的任务。我也在尝试改进我的语言处理能力,以
便更好地与人交流。你呢?
Human: 只是在写一些文档!
AI:
> Finished chain.
' 哦,看起来很有趣!你在写什么文档?'
示例代码
conversation_with_summary.predict(input="你听说过 LangChain 吗?")
输出结果
> Entering new ConversationChain chain...
Prompt after formatting:
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.
Current conversation:
Human: 嗨,最近怎么样?
AI: 嗨!最近还不错,我在学习新的技能,并且正在尝试新的任务。我也在尝试改进我的语言处理能力,以
便更好地与人交流。你呢?
Human: 只是在写一些文档!
AI: 哦,看起来很有趣!你在写什么文档?
Human: 你听说过 LangChain 吗?
AI:
> Finished chain.
' 是的,我知道LangChain。它是一个基于区块链的语言学习平台,旨在帮助人们更好地学习外语。你正在
写关于LangChain的文档吗?'
示例代码
# 我们可以看到,摘要和缓冲区都已更新
conversation_with_summary.predict(
input="哈哈,不对,不过很多人都把它混淆了。"
)
输出结果
> Entering new ConversationChain chain...
Prompt after formatting:
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.
Current conversation:
System:
The AI thinks artificial intelligence is a force for good because it will help
humans reach their full potential. It has been learning new skills and trying
new tasks, as well as attempting to improve its language processing so that it
can communicate better with humans. The human asks how the AI has been doing
recently.
Human: 只是在写一些文档!
AI: 哦,看起来很有趣!你在写什么文档?
Human: 你听说过 LangChain 吗?
AI: 是的,我知道LangChain。它是一个基于区块链的语言学习平台,旨在帮助人们更好地学习外语。你正
在写关于LangChain的文档吗?
Human: 哈哈,不对,不过很多人都把它混淆了。
AI:
> Finished chain.
' 哦,我明白了。那你在写什么文档呢?'
会话令牌缓冲ConversationTokenBufferMemory
在内存中保留最近交互的缓冲区,并使用令牌长度而不是交互次数来确定何时刷新交互。
memory = ConversationTokenBufferMemory(llm=llm, max_token_limit=10)
memory.save_context({"input": "hi"}, {"output": "whats up"})
memory.save_context({"input": "not much you"}, {"output": "not much"})
memory.load_memory_variables({})
{'history': 'Human: not much you\nAI: not much'}
我们还可以将历史记录作为消息列表获取(如果您正在与聊天模型一起使用,这将非常有用)。
memory = ConversationTokenBufferMemory(
llm=llm, max_token_limit=10, return_messages=True
)
memory.save_context({"input": "hi"}, {"output": "whats up"})
memory.save_context({"input": "not much you"}, {"output": "not much"})
ConversationTokenBufferMemory 在链中使用
让我们通过一个示例来说明,再次设置 verbose=True 以便我们可以看到提示。
from langchain.chains import ConversationChain
from langchain.memory import ConversationTokenBufferMemory
from langchain_community.chat_models import ChatOllama
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
conversation_with_summary = ConversationChain(
llm=llm,
# We set a very low max_token_limit for the purposes of testing.
memory=ConversationTokenBufferMemory(llm=llm, max_token_limit=60),
verbose=True,
)
conversation_with_summary.predict(input="Hi, what's up?")
对话知识图谱记忆ConversationKGMemory
前面讨论的几种记忆机制,都是在自然语言的水平上进行对话历史的记录和重用。那么如果能直接建模并 记忆更抽象的知识和关系,将是更高一层的进化。
ConversationKGMemory实现了这一目标。
ConversationKGMemory将对话中的实体和事件抽取出来,构建知识图谱;并在回答问题时,探索知识图谱 寻找相关记忆。
from langchain.chains.conversation.memory import ConversationKGMemory
from langchain.chains import ConversationChain
from langchain.prompts.prompt import PromptTemplate
from langchain_community.chat_models import ChatOllama
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
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. The AI ONLY uses information contained in the "Relevant Information"
section and does not hallucinate.
Relevant Information:
{history}
Conversation:
Human: {input}
AI:
"""
prompt = PromptTemplate(
input_variables=["history", "input"], template=template)
conversation_with_kg = ConversationChain(
llm=llm,
verbose=True,
prompt=prompt,
memory=ConversationKGMemory(llm=llm)
)
conversation_with_kg("我姐姐明天要过生日,我需要一束生日花束。")
conversation_with_kg("她喜欢粉色玫瑰,颜色是粉色的。")
conversation_with_kg("我又来了,还记得我昨天为什么要来买花吗?")
print(conversation_with_kg.memory.kg)
print(conversation_with_kg.memory.kg.get_triples())
多次对话过后,模型进行了多个概念的关系组合,形成了一个小型的知识网络,执行 结果截图如下:
机器人的记忆获得了概念化的升华,从低级的语言序列,上升为关系网络的知识模型。这也使得记忆的应用更加灵活:
- 可以进行知识推理,呈现更多潜在关联
- 记忆容量也得到扩展
基于向量存储记忆 VectorStoreRetrieverMemory
它是将所有之前的对话通过向量的方式存储到 VectorDB(向量数据库)中,在每一轮新的对话中,会根据用户的输入信息,匹配向量数据库中最相似的 K 组对话。
VectorStoreRetrieverMemory
将内存存储在 VectorDB 中,并在每次调用时查询前 K 个最“显著”的文档。
与大多数其他内存类不同的是,它不明确跟踪交互的顺序。
在这种情况下,“文档”是先前的对话片段。这对于引用 AI 在对话中早些时候被告知的相关信息是有用的。
http://docs.autoinfra.cn/docs/modules/memory/types/vectorstore_retriever_memory
from datetime import datetime
from langchain_openai import OpenAIEmbeddings
from langchain_openai import OpenAI
from langchain.memory import VectorStoreRetrieverMemory
from langchain.chains import ConversationChain
from langchain_core.prompts import PromptTemplate
初始化您的 VectorStore
根据您选择的存储,此步骤可能会有所不同。请参考相关的VectorStore文档以获取更多详细信息。
import faiss
from langchain_community.docstore import InMemoryDocstore
from langchain_community.vectorstores import FAISS
embedding_size = 1536 # Dimensions of the OpenAIEmbeddings
index = faiss.IndexFlatL2(embedding_size)
embedding_fn = OpenAIEmbeddings().embed_query
vectorstore = FAISS(embedding_fn, index, InMemoryDocstore({}), {})
创建您的VectorStoreRetrieverMemory
从任何VectorStoreRetriever实例化内存对象。
# 在实际使用中,您可以将`k`设置为较高的值,但我们使用k=1来显示向量查找仍然返回语义相关信息
retriever = vectorstore.as_retriever(search_kwargs=dict(k=1))
memory = VectorStoreRetrieverMemory(retriever=retriever)
# 当添加到代理时,内存对象可以保存对话或使用工具的相关信息
memory.save_context({"input": "我最喜欢的食物是披萨"}, {"output": "好的,我知道了"})
memory.save_context({"input": "我最喜欢的运动是足球"}, {"output": "..."})
memory.save_context({"input": "我不喜欢凯尔特人队"}, {"output": "好的"})
# 注意,返回的第一个结果是与税务帮助相关的记忆,语言模型认为这比其他文档更具语义相关性,尽管它们都包含数字。
print(memory.load_memory_variables({"prompt": "我应该看什么运动?"})["history"])
在链中使用
让我们通过一个例子来说明,再次设置 verbose=True 以便我们可以看到提示。
llm = OpenAI(temperature=0) # 可以是任何有效的LLM
_DEFAULT_TEMPLATE = """以下是人类和AI之间友好对话的一部分。AI健谈并从其上下文中提供了许多具
体细节。如果AI不知道问题的答案,它会诚实地说不知道。
先前对话的相关部分:
{history}
(如果不相关,您不需要使用这些信息)
当前对话:
人类:{input}
AI:"""
PROMPT = PromptTemplate(
input_variables=["history", "input"], template=_DEFAULT_TEMPLATE
)
conversation_with_summary = ConversationChain(
llm=llm,
prompt=PROMPT,
# 为了测试目的,我们设置了一个非常低的max_token_limit。
memory=memory,
verbose=True
)
conversation_with_summary.predict(input="嗨,我叫佩里,你好吗?")
> 进入新的ConversationChain链...
格式化后的提示:
以下是人类和AI之间友好对话的一部分。AI健谈并从其上下文中提供了许多具体细节。如果AI不知道问
题的答案,它会诚实地说不知道。
先前对话的相关部分:
input: 我最喜欢的食物是披萨
output: 好的,我知道了
(如果不相关,您不需要使用这些信息)
当前对话:
人类:嗨,我叫佩里,你好吗?
AI:
> 完成链。
"嗨佩里,我很好。你呢?"
在这里,与篮球相关的内容被展示出来
conversation_with_summary.predict(input="我的最喜欢的运动是什么?")
> 进入新的ConversationChain链...
格式化后的提示:
以下是人类和AI之间友好对话的一部分。AI健谈并从其上下文中提供了许多具体细节。如果AI不知道问
题的答案,它会诚实地说不知道。
先前对话的相关部分:
input: 我最喜欢的运动是足球
output: ...
(如果不相关,您不需要使用这些信息)
当前对话:
人类:我的最喜欢的运动是什么?
AI:
> 完成链。
'你之前告诉我你最喜欢的运动是足球。'
尽管语言模型是无状态的,但由于获取了相关的记忆,它可以“推理”时间
conversation_with_summary.predict(input="我的最喜欢的食物是什么")
> 进入新的ConversationChain链...
格式化后的提示:
以下是人类和AI之间友好对话的一部分。AI健谈并从其上下文中提供了许多具体细节。如果AI不知道问
题的答案,它会诚实地说不知道。
先前对话的相关部分:
input: 我最喜欢的食物是披萨
output: 好的,我知道了
(如果不相关,您不需要使用这些信息)
当前对话:
人类:我的最喜欢的食物是什么
AI:
> 完成链。
'你说你最喜欢的食物是披萨。'
对话中的记忆会自动存储,由于此查询最符合上述介绍聊天,代理能够“记住”用户的姓名
conversation_with_summary.predict(input="我的名字是什么?")
> 进入新的ConversationChain链...
格式化后的提示:
以下是人类和AI之间友好对话的一部分。AI健谈并从其上下文中提供了许多具体细节。如果AI不知道问
题的答案,它会诚实地说不知道。
先前对话的相关部分:
input: 嗨,我叫佩里,你好吗?
response: 嗨佩里,我很好。你呢?
(如果不相关,您不需要使用这些信息)
当前对话:
人类:我的名字是什么?
AI:
> 完成链。
'你的名字是佩里。'
Agents(代理)
一些应用程序需要根据用户输入灵活地调用 LLM 和其他工具的链。
代理接口为这样的应用程序提供了灵活性。
代理可以访问一套工具,并根据用户输入确定要使用哪些工具。我们可以简单的理解为他可以动态的帮 我们选择和调用 chain 或者已有的工具。
代理主要有两种类型 Action agents 和 Plan-and-execute agents。
Action agents
行为代理: 在每个时间步,使用所有先前动作的输出来决定下一个动作。
下图展示了行为代理执行的流程。
Plan-and-execute agents
预先决定完整的操作顺序,然后执行所有操作而不更新计划,下面是其流程。
- 接收用户输入
- 计划要采取的完整步骤顺序
- 按顺序执行步骤,将过去步骤的输出作为未来步骤的输入传递
Langchain 实战
读取向量库进行RAG查询信息
可以参考
https://python.langchain.com/v0.2/docs/tutorials/qa_chat_history/#tying-it-together
https://python.langchain.com/v0.2/docs/how_to/message_history/
import chromadb
from langchain.chains import create_history_aware_retriever, create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_community.chat_models import ChatOllama
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.llms.ollama import Ollama
from langchain_community.vectorstores import Chroma
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnablePassthrough
from langchain_core.runnables.history import RunnableWithMessageHistory
# RAG方式-查询PDF内容
def modelResponse3(message):
llm = ChatOllama(base_url='http://10.4.3.41:11434', model="llama2-chinese:13b")
# 高性能开放嵌入模型
# embeddings = OllamaEmbeddings(base_url='http://10.4.1.15:11434', model="mxbai-embed-large")
embeddings = OllamaEmbeddings(base_url='http://10.4.3.41:11434', model="nomic-embed-text")
persistent_client = chromadb.HttpClient(host='10.4.3.41', port=8000)
# 创建向量数据库
docsearch = Chroma(client=persistent_client, collection_name="Security8",embedding_function=embeddings)
# 指定 search_type="similarity" 表示使用相似度检索,,search_kwargs={"k": 6} 表示最多返回 6 个结果
retriever = docsearch.as_retriever(search_type="similarity",search_kwargs={'k': 6})
# 提示模版
system_prompt = (
"你是负责回答问题的助手"
"使用以下检索到的上下文片段来回答问题"
"这个问题。如果你不知道答案,就说你不知道"
"回答更可能的详细,保持句子的连贯性"
"\n\n"
"{context}"
)
# prompt = ChatPromptTemplate.from_messages(
# [
# ("system", system_prompt),
# ("human", "{input}"),
# ]
# )
# 该链获取文档列表并将它们全部格式化为提示符,然后将该提示符传递给LLM. 它传递所有文档,所以您应该确保它适合您正在使用的LLM的上下文窗口。
# question_answer_chain = create_stuff_documents_chain(llm, prompt)
# 该链接收用户查询,然后将其传递给检索器以获取相关文档。然后将这些文档(和原始输入)传递给LLM以生成响应
# rag_chain = create_retrieval_chain(retriever, question_answer_chain)
# response = rag_chain.invoke({"input": message})
# print(response["answer"])
# 上下文化系统提示
contextualize_q_system_prompt = (
"给出聊天记录和最新的用户问题 "
"哪些可能会引用聊天历史记录中的上下文, "
"提出一个可以理解的独立问题 "
"没有聊天记录。不回答问题, "
"如果需要,只需重新制定它,否则就原样返回."
)
# 拼接模版
contextualize_q_prompt = ChatPromptTemplate.from_messages(
[
("system", contextualize_q_system_prompt),
MessagesPlaceholder("chat_history"),
("human", "{input}"),
]
)
# 历史检索器
history_aware_retriever = create_history_aware_retriever(
llm, retriever, contextualize_q_prompt
)
# 查询模版
qa_prompt = ChatPromptTemplate.from_messages(
[
("system", system_prompt),
MessagesPlaceholder("chat_history"),
("human", "{input}"),
]
)
# 创建素材文档链
question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)
# 创建检索链
rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)
chat_history = []
ai_msg_1 = rag_chain.invoke({"input": message, "chat_history": chat_history})
chat_history.extend(
[
HumanMessage(content=message),
AIMessage(content=ai_msg_1["answer"]),
]
)
second_question = "我刚刚的问题是什么?"
ai_msg_2 = rag_chain.invoke({"input": second_question, "chat_history": chat_history})
print(chat_history)
print(ai_msg_2["answer"])