系列文章索引
LangChain教程 - 系列文章
前提条件
在开始本教程之前,您需要对以下概念有所了解:
- 聊天模型:大语言模型(LLM)是支持聊天机器人的核心。
- Prompt 模板:用于引导模型如何响应用户输入。
- 聊天历史记录:保存用户和模型之间的对话,以便后续调用。
概述
在本教程中,我们将逐步讲解如何设计和实现一个基于 LLM 的聊天机器人。该聊天机器人不仅可以进行对话,还能够记住之前的交互内容。
相关概念
- Conversational RAG:实现一个能够基于外部数据源进行对话的机器人。
- Agents:构建一个可以执行任务的聊天机器人。
本教程将涵盖聊天机器人的基础内容,帮助您逐步理解更高级的应用,如 Conversational RAG 和 Agents。
环境设置
本教程推荐使用 Jupyter Notebook 来运行代码示例。这是因为在使用 LLM 时,可能会遇到诸如输出不符合预期、API 故障等问题,交互式环境可以更好地帮助您理解和调试代码。
安装依赖
首先,确保已经安装以下库:
pip install langchain langchain_community langchain-openai
LangSmith
构建聊天机器人时,随着模型调用步骤的增加,了解链条内部的工作变得至关重要。LangSmith 是一个用于监控模型调用链路的工具。可以通过以下方式启用 LangSmith:
import getpass
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = getpass.getpass()
快速开始
首先,我们学习如何使用一个语言模型。
LangChain 支持多种语言模型,您可以根据需求选择合适的模型,例如 OpenAI 的 GPT-3.5 Turbo:
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-3.5-turbo")
简单对话示例
使用 model.invoke
方法来调用模型,并传递消息:
from langchain_core.messages import HumanMessage
response = model.invoke([HumanMessage(content="Hi! I'm Bob")])
print(response.content)
模型将输出类似 “Hello Bob! How can I assist you today?” 的响应。
管理对话状态
直接使用模型并不能记住先前的对话,因此无法回答基于上下文的问题。例如,如果我们接下来问:
response = model.invoke([HumanMessage(content="What's my name?")])
print(response.content)
模型会返回 “I don’t have access to your personal information”。
为了让聊天机器人记住上下文,我们可以传入整个对话历史:
from langchain_core.messages import AIMessage
response = model.invoke([
HumanMessage(content="Hi! I'm Bob"),
AIMessage(content="Hello Bob! How can I assist you today?"),
HumanMessage(content="What's my name?")
])
print(response.content)
现在,模型可以正确回答:“Your name is Bob.”
消息历史
要管理对话的状态,我们可以使用 Message History 类。这个类将跟踪用户和模型的输入和输出,并将它们存储在某种数据存储中。
首先,安装 langchain-community,我们将用到其中的消息历史存储功能:
pip install langchain_community
然后,设置一个内存中的聊天历史对象:
from langchain_core.chat_history import InMemoryChatMessageHistory, BaseChatMessageHistory
store = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
将聊天历史与模型结合:
from langchain_core.runnables.history import RunnableWithMessageHistory
with_message_history = RunnableWithMessageHistory(model, get_session_history)
每次调用模型时,我们需要为其提供一个 session_id
以区分不同的对话会话:
config = {"configurable": {"session_id": "abc2"}}
response = with_message_history.invoke([HumanMessage(content="Hi! I'm Bob")], config=config)
print(response.content)
现在聊天机器人可以记住对话信息。再次调用时,它可以记住名字:
response = with_message_history.invoke([HumanMessage(content="What's my name?")], config=config)
print(response.content)
使用 Prompt 模板
我们可以使用 Prompt Templates 来自定义模型的响应规则。添加一个系统消息,用于指导聊天机器人:
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant. Answer all questions in {language}."),
MessagesPlaceholder(variable_name="messages")
])
chain = prompt | model
将历史记录功能与 Prompt 模板结合:
with_message_history = RunnableWithMessageHistory(chain, get_session_history)
config = {"configurable": {"session_id": "abc5"}}
response = with_message_history.invoke([HumanMessage(content="Hi! I'm Jim")], config=config)
print(response.content)
可以继续扩展 Prompt 模板,例如支持多语言功能:
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant. Answer all questions in {language}."),
MessagesPlaceholder(variable_name="messages")
])
chain = prompt | model
response = chain.invoke({"messages": [HumanMessage(content="Hi! I'm Bob")], "language": "Spanish"})
print(response.content)
模型将以西班牙语回答:“¡Hola, Bob! ¿En qué puedo ayudarte hoy?”
管理聊天历史长度
聊天记录可能会不断增加,导致超出 LLM 的上下文窗口。因此,限制消息数量非常重要。我们可以使用 trim_messages 函数来修剪对话历史:
from langchain_core.messages import trim_messages, SystemMessage
messages = [
SystemMessage(content="You're a good assistant"),
HumanMessage(content="Hi! I'm Bob"),
# 更多消息...
]
trimmed_messages = trim_messages(messages, max_tokens=65, strategy="last")
使用这个修剪后的消息历史进行模型调用,可以避免上下文超出限制。
流式输出
为了提升用户体验,可以使用模型的 stream
方法来逐步返回响应:
config = {"configurable": {"session_id": "abc15"}}
for r in with_message_history.stream({"messages": [HumanMessage(content="Hi! I'm Todd. Tell me a joke.")], "language": "English"}, config=config):
print(r.content, end="")
模型将流式返回每个生成的 token,提高响应的实时性。
完整代码
import os
import getpass
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, trim_messages
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.chat_history import InMemoryChatMessageHistory, BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
# 设置 OpenAI API 密钥
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API key: ")
# 创建一个聊天模型(使用 OpenAI GPT-3.5)
model = ChatOpenAI(model="gpt-3.5-turbo")
# 消息历史存储
store = {}
# 获取会话历史记录
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
# 创建带有 Prompt 模板的链
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant. Answer all questions in {language}."),
MessagesPlaceholder(variable_name="messages")
])
# 将消息修剪工具添加到链中,避免消息过多超出上下文窗口
trimmer = trim_messages(
max_tokens=100, # 限制最大 token 数
strategy="last", # 保留最后的消息
token_counter=model, # 使用模型计算 token
include_system=True,
allow_partial=False,
start_on="human" # 从人类消息开始修剪
)
# 定义处理消息历史和修剪功能的链
from operator import itemgetter
from langchain_core.runnables import RunnablePassthrough
chain = (
RunnablePassthrough.assign(messages=itemgetter("messages") | trimmer) # 先修剪消息
| prompt # 然后传递到 Prompt 模板
| model # 调用模型生成响应
)
# 将消息历史包装到链中
with_message_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="messages"
)
# 测试会话:输入用户消息,并指定语言为西班牙语
config = {"configurable": {"session_id": "session1"}}
# 初始消息
response = with_message_history.invoke(
{"messages": [HumanMessage(content="
Hi! I'm Todd.")], "language": "Spanish"},
config=config,
)
print("Response:", response.content)
# 继续对话
response = with_message_history.invoke(
{"messages": [HumanMessage(content="What's my name?")], "language": "Spanish"},
config=config,
)
print("Response:", response.content)
# 流式响应
print("\nStreaming Response:")
for r in with_message_history.stream(
{
"messages": [HumanMessage(content="Tell me a joke.")],
"language": "English"
},
config=config,
):
print(r.content, end="")
通过以上步骤,您已经构建了一个能够记住对话历史、支持多语言、并且具有流式输出的 LLM 驱动聊天机器人。