聊天机器人将能够进行对话并记住之前的交互。请注意,我们构建的这个聊天机器人将仅使用语言模型进行对话。
以下是我们将要使用的一些高级组件:
- Chat Models:聊天机器人界面基于消息而不是原始文本,因此最适合聊天模型而不是文本法学硕士。 Prompt
- Templates:它简化了组合默认消息、用户输入、聊天历史记录和(可选)附加检索上下文的提示的过程。
- ChatHistory:这使得聊天机器人能够“记住”过去的互动,并在回答后续问题时将其考虑在内。
step1 导入相关包和加载模型
import getpass
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = 'you key'
from langchain_openai import ChatOpenAI
chat = ChatOpenAI()
step2 构建一个简单的聊天信息
from langchain_core.messages import HumanMessage
chat.invoke([HumanMessage(content="Hi! I'm Bob")])
输出内容为
AIMessage(content=‘Hello Bob! How can I assist you today?’,
response_metadata={‘token_usage’: {‘completion_tokens’: 10,
‘prompt_tokens’: 12, ‘total_tokens’: 22}, ‘model_name’:
‘gpt-3.5-turbo’, ‘system_fingerprint’: ‘fp_2f57f81c11’,
‘finish_reason’: ‘stop’, ‘logprobs’: None},
id=‘run-28b10d8e-3461-43d9-9fac-f516392c0a24-0’)
但是现在模型对于上下文没有任何概念,例如我接下来问
chat.invoke([HumanMessage(content="What's my name?")])
他的回答是
AIMessage(content=“I’m sorry, I do not have access to personal
information. If you would like to share your name with me, feel free
to do so.”, response_metadata={‘token_usage’: {‘completion_tokens’:
30, ‘prompt_tokens’: 12, ‘total_tokens’: 42}, ‘model_name’:
‘gpt-3.5-turbo’, ‘system_fingerprint’: None, ‘finish_reason’: ‘stop’,
‘logprobs’: None}, id=‘run-a97abc9c-9957-4acc-bf27-183d9d31277e-0’)
可以看出,它没有将前面的对话转入上下文,无法回答问题。这会带来糟糕的聊天机器人体验!
为了解决这个问题,我们需要将整个对话历史记录传递到模型中。让我们看看这样做时会发生什么:
message = [
HumanMessage(content="Hi! I'm Bob"),
AIMessage(content="Hello Bob! How can I assist you today?"),
HumanMessage(content="What's my name?")
]
chat.invoke(message)
AIMessage(content=‘Your name is Bob. How can I assist you today,
Bob?’, response_metadata={‘token_usage’: {‘completion_tokens’: 14,
‘prompt_tokens’: 35, ‘total_tokens’: 49}, ‘model_name’:
‘gpt-3.5-turbo’, ‘system_fingerprint’: ‘fp_2f57f81c11’,
‘finish_reason’: ‘stop’, ‘logprobs’: None},
id=‘run-88b5c71b-dbe1-40c5-a624-cb233b2029bc-0’)
step3 创建临时缓冲,存储历史消息
我们使用Message History class 包装我们的模型,包装后的模型将跟踪模型的输入和输出,并将它们存储在某个数据存储中。未来的交互将加载这些消息并将它们作为输入的一部分传递到链中。
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
store ={}
def get_session_history(session_id:str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[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)
输出结果为
Hello Bob! How can I assist you today? Your name is Bob. You mentioned
it in your initial message. How can I help you further, Bob?
step4 增加prompt
提示模板有助于将用户原始信息转换为 LLM 可以使用的格式。在这种情况下,用户原始输入只是一条消息,我们将它传递给 LLM。现在让我们让它更复杂一点。首先,让我们添加一条带有一些自定义指令的system消息(但仍然将消息作为输入)。接下来,除了消息之外,我们还将添加更多输入。
首先,让我们添加一条系统消息。为此,我们将创建一个 ChatPromptTemplate。我们将利用它MessagesPlaceholder来传递所有消息。
from langchain_core.prompts import ChatPromptTemplate,MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages(
[
("system","You are a helpful assistant. Answer all questions to the best of your ability.",),
MessagesPlaceholder(variable_name="messages")
]
)
chain = prompt | chat
chain.invoke({"messages": [HumanMessage(content="hi! I'm bob")]})
现在让我们的提示变得更复杂一些。我们假设提示模板现在看起来像这样:
prompt = ChatPromptTemplate.from_messages(
[
("system","You are a helpful assistant. Answer all questions to the best of your ability in {language}."),
MessagesPlaceholder(variable_name="messages")
]
)
chain = prompt |chat
chain.invoke({"messages":[HumanMessage(content="Hi! I'm Bob")],"language":"Spanish"}).content
输出结果:
‘Hola, Bob. ¿En qué puedo ayudarte?’
现在让我们将这个更复杂的链包装在消息历史记录类中。这次,因为输入中有多个键,所以我们需要指定正确的键来保存聊天记录
with_message_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="messages"
)
config = {"configurable":{"session_id":"abv"}}
response = with_message_history.invoke(
{"messages": [HumanMessage(content="hi! I'm todd")], "language": "Spanish"},
config=config,
)
print(response.content)
response = with_message_history.invoke(
{"messages": [HumanMessage(content="whats my name?")], "language": "Spanish"},
config=config,
)
print(response.content)
输出结果
¡Hola Todd! ¿En qué puedo ayudarte hoy?
Tu nombre es Todd.
管理对话
构建聊天机器人时需要理解的一个重要概念是如何管理对话历史记录。如果不进行管理,消息列表将无限增长,并可能溢出 LLM 的上下文窗口。因此,添加一个步骤来限制您传入的消息的大小非常重要。
from langchain_core.runnables import RunnablePassthrough
def filter_message(message,k=10):
print(message)
return message[-k:]
chain = (
RunnablePassthrough.assign(messages=lambda x:filter_message(x['messages']))
|prompt
| chat
)
messages = [
HumanMessage(content="hi! I'm bob"),
AIMessage(content="hi!"),
HumanMessage(content="I like vanilla ice cream"),
AIMessage(content="nice"),
HumanMessage(content="whats 2 + 2"),
AIMessage(content="4"),
HumanMessage(content="thanks"),
AIMessage(content="no problem!"),
HumanMessage(content="having fun?"),
AIMessage(content="yes!"),
]
response = chain.invoke(
{
"messages":messages +[HumanMessage(content="what's my name?")],
"language":"english"
}
)
response.content
输出内容:
“I’m sorry, but I do not have access to that information. How can I assist you today?”