3.4 聊天模型
聊天模型是LangChain框架的核心组件之一,专门用于处理聊天消息,即其输入和输出都是聊天消息格式,而不是简单的文本。
3.4.1 消息类型(Message types)
在ChatModels中,输入的是一个消息列表,输出的是一个消息。所有消息都包含一个角色(role)和一个内容(content)属性,具体说明如下所示。
(1)角色(Role):角色描述了是谁在发送这条消息,LangChain为不同角色定义了不同的消息类。
(2)内容(Content):内容属性描述了消息的内容,可以是以下几种形式:
- 一个字符串(大多数模型处理这种类型的内容)。
- 一个字典列表(用于多模态输入,字典中包含有关输入类型和输入位置的信息)。
(3)额外参数(additional_kwargs):消息还可以有一个额外的参数属性,用于传递有关消息的其他信息。这主要用于特定于提供商的输入参数,而不是通用的。一个常见的额外参数例子是OpenAI的function_call。
在LangChain中,常用的消息类型(Message types)如下所示。
- HumanMessage(用户消息):表示用户发送的消息,通常只包含内容。
- AIMessage(模型消息):表示模型发送的消息。可能具有additional_kwargs属性,例如OpenAI工具调用。
- SystemMessage(系统消息):表示系统消息,告诉模型如何行动。通常只包含内容。并非所有模型都支持此功能。
- FunctionMessage(函数消息):表示函数调用的结果。除了角色和内容外,此消息还具有一个名称参数,表示产生此结果的函数的名称。
- ToolMessage(工具消息):表示工具调用的结果。与FunctionMessage不同,这是为了匹配OpenAI的函数和工具消息类型。除了角色和内容外,此消息还具有tool_call_id参数,表示产生此结果的工具调用的ID。
3.4.2 函数调用(Function calling)
越来越多的聊天模型,比如OpenAI、Gemini等,都提供了函数调用API。函数调用是指使用聊天模型的API来描述函数及其参数,并让模型返回一个JSON对象,其中包含要调用的函数和该函数的输入。这在构建工具使用链和代理以及从模型中获取结构化输出时非常有用。
在LangChain中,提供了一系列实用工具来简化函数调用过程,具体说明如下所示。
- 简单的语法绑定函数到模型:LangChain提供了简单的语法来将函数绑定到模型上,使得在每次模型被调用时都能传递这些函数。
- 格式化转换器:用于将各种类型的对象格式化为预期的函数模式。
- 输出解析器:用于从API响应中提取函数调用。
- 构建在函数调用之上的链条:用于从模型中获取结构化输出。
假设有一个简单的乘法函数和一个聊天模型(假设为OpenAI的ChatOpenAI模型),下面的例子演示了使用LangChain进行函数调用的过程。
实例3-1:调用OpenAI执行乘法运算(源码路径:codes\3\mo01.py)
实例文件mo01.py的具体实现代码如下所示。
from langchain_openai import ChatOpenAI
from langchain_core.utils.function_calling import convert_to_openai_tool
# 定义乘法函数
def multiply(a: int, b: int) -> int:
"""Multiply two integers together."""
return a * b
# 创建聊天模型实例
llm = ChatOpenAI(model="gpt-3.5-turbo-0125")
# 将函数转换为适用于OpenAI的JSON模式
multiply_tool = convert_to_openai_tool(multiply)
# 将函数绑定到聊天模型上
llm_with_multiply_tool = llm.bind_tools([multiply_tool], tool_choice="multiply")
# 调用聊天模型并调用绑定的函数
response = llm_with_multiply_tool.invoke("calculate 5 times 10")
# 打印响应
print(response)
在上述代码中,首先定义了一个名为multiply的乘法函数,然后创建了一个ChatOpenAI聊天模型实例。接着,使用convert_to_openai_tool函数将乘法函数转换为OpenAI所需的JSON格式。最后,将转换后的函数绑定到聊天模型上,并通过调用聊天模型来触发函数调用。执行后会输出:
AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_f3DApOzb60iYjTfOhVFhDRMI', 'function': {'arguments': '{"a":5,"b":10}', 'name': 'Multiply'}, 'type': 'function'}]})
3.4.3 缓存
Caching 是一种在计算机科学和软件工程中常用的技术,用于提高系统的性能和效率。在聊天模型(Chat Models)的上下文中,缓存层(caching layer)的作用是存储之前请求和响应的结果,以便在后续的相同请求时可以直接提供结果,而无需再次执行计算或调用外部服务。
在 LangChain 中,缓存层的作用主要有两个方面:
- 节省成本:如果你的应用程序经常需要重复相同的请求,缓存可以帮助减少对大型语言模型(LLM)提供商的 API 调用次数。由于每次 API 调用都可能涉及费用,减少调用次数可以降低成本。
- 提高速度:通过缓存,当相同的请求再次出现时,应用程序可以直接从缓存中获取结果,而不需要再次向 LLM 提供商发送请求。这样可以显著减少响应时间,提高用户体验。
LangChain提供了两种主要的缓存方式,具体说明如下所示。
- 内存缓存:内存缓存将数据存储在应用程序的内存中,可以快速地读取和写入数据。它适用于小型数据集和需要快速访问的情况,但在应用程序重启时数据会丢失。
- SQLite 缓存:SQLite 缓存将数据存储在本地 SQLite 数据库中,可以持久化存储大量数据,并且在应用程序重启后数据依然保留。它适用于需要长期存储和大量数据的场景,但相比内存缓存,读取和写入数据的速度可能会稍慢一些。
例如下面是一个使用 LangChain 进行缓存的例子,在这个例子中,将使用内存缓存来存储和检索用户与聊天机器人的交互。
实例3-1:使用缓存存储和检索用户与聊天机器人的交互(源码路径:codes\3\mo02.py)
实例文件mo02.py的具体实现代码如下所示。
import getpass
import os
from langchain_openai import ChatOpenAI
from langchain.globals import set_llm_cache
# 假设您已经有了 TogetherAI 的 API 密钥
os.environ["TOGETHER_API_KEY"] = getpass.getpass("请输入您的TogetherAI API密钥: ")
# 初始化与 TogetherAI 的连接,使用 MistralAI 的模型
llm = ChatOpenAI(
base_url="https://api.together.xyz/v1",
api_key=os.environ["TOGETHER_API_KEY"],
model="mistralai/Mixtral-8x7B-Instruct-v0.1",
)
# 设置内存缓存
set_llm_cache(InMemoryCache())
# 定义一个函数,用于处理用户的查询并使用缓存
def handle_user_query(query):
# 尝试从缓存中获取响应
response = llm.cache.get(query)
if response is not None:
print(f"从缓存中获取: {response}")
return response
# 如果缓存中没有,就向模型请求一个新的响应
response = llm.predict(query)
# 将新的响应存储到缓存中
llm.cache.set(query, response)
print(f"从模型获取: {response}")
return response
# 模拟用户查询
user_queries = ["今天天气怎么样?", "今天天气怎么样?", "北京明天会下雨吗?"]
for query in user_queries:
response = handle_user_query(query)
print(response)
上述代码的实现流程如下所示:
- 首先,设置了环境变量来存储 TogetherAI 的 API 密钥,并创建了一个 ChatOpenAI 实例来与 TogetherAI 的 API 进行交互。此处使用了 MistralAI 的 "Mixtral-8x7B-Instruct-v0.1" 模型。
- 然后,通过 set_llm_cache(InMemoryCache()) 设置了内存缓存,定义了一个 handle_user_query 函数来处理用户的查询。如果查询已经在缓存中,我们就从缓存中获取响应;如果不在,就向模型请求一个新的响应,并将这个响应存储到缓存中。
- 最后,模拟了三个用户查询,其中包含两次相同的查询。第一次查询会触发模型的调用,而后续的相同查询将直接从缓存中获取响应,从而展示了缓存的效果。执行后会输出:
从模型获取: 今天天气很好,适合外出活动。
从缓存中获取: 今天天气很好,适合外出活动。
从模型获取: 北京明天可能会有小雨,请记得带伞。
在上述模拟输出中,用户首先询问了关于今天的天气情况,由于这是第一次查询,缓存中没有结果,所以调用了模型并得到了响应,同时将结果存储到了缓存中。接着,用户再次询问相同的问题。这次,因为已经将结果存储在了缓存中,所以直接从缓存中获取了响应,而没有再次调用模型。最后,用户提出了一个新的问题关于北京明天的天气情况。这个问题之前没有被问过,所以需要再次调用模型来获取新的响应,并将这个新的响应存储到缓存中。
未完待续