Messages:消息
上面调用LLM的方式,都是通过向invoke
函数传入一个string
参数,实现text-in, text-out
(文本输入,文本输出),这是比较传统、老式的completion接口;现在LLM主推的API接口,都是所谓的Chat Completion Models方式,实现messages in, message out
(消息输入,消息输出)。直接看例子🌰:
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
load_dotenv()
# LLM style: text in, text out
model = ChatOpenAI(model="gpt-4o")
result = model.invoke('What is 81 divided by 9?')
print("\n== LLM style: text in, text out ==\n")
print(result)
# SystemMessage:
# Message for priming AI behavior, usually passed in as the first of a sequenc of input messages.
# HumanMessagse:
# Message from a human to the AI model.
messages = [
SystemMessage(content="Solve the following math problems"),
HumanMessage(content="What is 81 divided by 9?"),
]
result = model.invoke(messages)
print("\n== Chat Model style: messages in, message out ==\n")
print(result)
== LLM style: text in, text out ==
content='81 divided by 9 equals 9.' response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 16, 'total_tokens': 25}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_d33f7b429e', 'finish_reason': 'stop', 'logprobs': None} id='run-9ca2ec2d-1dbf-4c29-a7c8-490a282ed9dd-0' usage_metadata={'input_tokens': 16, 'output_tokens': 9, 'total_tokens': 25}
== Chat Model style: messages in, message out ==
content="81 divided by 9 is 9. \n\nHere's the calculation:\n\n\\[ 81 \\div 9 = 9 \\]" response_metadata={'token_usage': {'completion_tokens': 26, 'prompt_tokens': 25, 'total_tokens': 51}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_dd932ca5d1', 'finish_reason': 'stop', 'logprobs': None} id='run-0389a581-4878-484b-827c-d59b50fd41e2-0' usage_metadata={'input_tokens': 25, 'output_tokens': 26, 'total_tokens': 51}
type(result)
langchain_core.messages.ai.AIMessage
在Chat Model的调用中,用户传入的参数是一个消息列表,支持的消息类型包括:
- SystemMessage:系统消息,用来设定AI的行为,一般放在消息列表的第一项。
- AIMessage:AI消息,用来表示模型的输出。上面例子中的result就是一个AIMessage,它会包含一个response_metadata成员,来说明token的消耗、模型名称、停止原因等等。
- HumanMessage:人类消息,用来表示用户的输入。LLM方式调用直接传入
invoke
的string,就会被LangChain包装成一个HumanMessage。 - FunctionMessage:在function call中会用到。
- ToolMessage:在tool call中会用到。
消息列表可以很好地支持Few Shot(通过举例,提升LLM的准确性,减少幻觉)的Prompt技巧:
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
# Few shot prompt messages
messages = [
SystemMessage(content="Solve the following math problems"),
HumanMessage(content="What is 81 divided by 9?"),
AIMessage(content="81 divided by 9 is 9."),
HumanMessage(content="What is 10 times 5?"),
]
# Invoke the model with messages
result = model.invoke(messages)
print(f"Answer from AI: {result.content}")
Answer from AI: 10 times 5 is 50.
在后面的编码中,我们会采用Messages这种更主流的API方式。之前的例子,用户和LLM的对话都是一次性的;使用Messages,我们可以方便地实现对话消息历史记录,从而增加Chat Model的记忆功能。
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain.schema import AIMessage, HumanMessage, SystemMessage
# Load environment variables from .env
load_dotenv()
# Create a ChatOpenAI model
model = ChatOpenAI(model="gpt-3.5-turbo")
chat_history = [] # Use a list to store messages
# Set an initial system message (optional)
system_message = SystemMessage(content="You are a helpful AI assistant.")
chat_history.append(system_message) # Add system message to chat history
# Chat loop
while True:
query = input("You: ")
if query.lower() == "exit":
break
chat_history.append(HumanMessage(content=query)) # Add user message
# Get AI response using history
result = model.invoke(chat_history)
response = result.content
chat_history.append(AIMessage(content=response)) # Add AI message
print(f"AI: {response}")
print("---- Message History ----")
print(chat_history)
You: Andy是一个喜欢编程、喜欢写作的工程师,你认识他吗?
AI: 抱歉,作为一个AI助手,我不认识任何具体的个人。不过,我可以帮助您了解关于编程、写作以及工程领域的信息。如果您有任何问题,都可以随时问我哦!
You: 81÷9=
AI: 81 ÷ 9 = 9
You: Andy的爱好是什么?
AI: 根据您之前提到的信息,Andy的爱好是编程和写作。这些爱好表明他对技术和创意有着浓厚的兴趣。如果您还想了解更多关于编程或写作方面的信息,可以随时向我提问。我将竭诚为您提供帮助!
You: exit
---- Message History ----
[SystemMessage(content='You are a helpful AI assistant.'), HumanMessage(content='Andy是一个喜欢编程、喜欢写作的工程师,你认识他吗?'), AIMessage(content='抱歉,作为一个AI助手,我不认识任何具体的个人。不过,我可以帮助您了解关于编程、写作以及工程领域的信息。如果您有任何问题,都可以随时问我哦!'), HumanMessage(content='81÷9= '), AIMessage(content='81 ÷ 9 = 9'), HumanMessage(content='Andy的爱好是什么?'), AIMessage(content='根据您之前提到的信息,Andy的爱好是编程和写作。这些爱好表明他对技术和创意有着浓厚的兴趣。如果您还想了解更多关于编程或写作方面的信息,可以随时向我提问。我将竭诚为您提供帮助!')]
在上面的例子中,我们手动维护了一个chat_history列表,将角色设定的SystemMessage、每轮对话的用户输入HumanMessage以及LLM的回复AIMessage都加入chat_history中,作为每次对话的消息列表,从而实现了AI聊天的记忆功能,让它记住了Andy的爱好。
Memory:记忆
内存
上面的聊天记录是手动维护的,LangChain提供了一系列内置方便存储和读取聊天记录的功能供开发者使用。下面是一个使用内存作为存储的例子:
from langchain_openai.chat_models import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
model = ChatOpenAI(model='gpt-3.5-turbo')
prompt = ChatPromptTemplate.from_messages(
[
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
]
)
runnable = prompt | model
store = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
def print_session_history(session_id: str) -> None:
text = f"Message History (session_id={session_id})"
print(f"\n----{text}----")
print(get_session_history(session_id))
print(f"----{'-' * len(text)}----")
with_message_history = RunnableWithMessageHistory(
runnable,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
session_id = 'abc123'
print_session_history(session_id)
result = with_message_history.invoke(
{"input": "我是Andy, 喜欢编程和写作。"},
config={"configurable": {"session_id": session_id}},
)
print(f"AI: {result.content}")
print_session_history(session_id)
result =with_message_history.invoke(
{"input": "我是谁?爱好是什么?"},
config={"configurable": {"session_id": session_id}},
)
print(f"AI: {result.content}")
session_id = 'abc456'
print_session_history(session_id)
result = with_message_history.invoke(
{"input": "我是谁?爱好是什么?"},
config={"configurable": {"session_id": session_id}},
)
print(f"AI: {result.content}")
----Message History (session_id=abc123)----
-------------------------------------------
AI: 很高兴认识你,Andy!编程和写作是非常有趣和创造性的爱好。你喜欢哪种编程语言?在写作方面,你更倾向于写小说还是技术文章呢?希望我们可以互相交流和分享我们的兴趣!如果有任何问题或者想讨论的话题,都可以随时告诉我。😊
----Message History (session_id=abc123)----
Human: 我是Andy, 喜欢编程和写作。
AI: 很高兴认识你,Andy!编程和写作是非常有趣和创造性的爱好。你喜欢哪种编程语言?在写作方面,你更倾向于写小说还是技术文章呢?希望我们可以互相交流和分享我们的兴趣!如果有任何问题或者想讨论的话题,都可以随时告诉我。😊
-------------------------------------------
AI: 您是Andy,您的爱好是编程和写作。您可以随时告诉我您想要讨论的话题,我会尽力和您进行交流和互动。如果有任何问题或者需要帮助的地方,也欢迎随时告诉我。让我们一起分享和探讨各种有趣的主题吧!😊
----Message History (session_id=abc456)----
-------------------------------------------
AI: 抱歉,我无法回答这个问题,因为我不了解你的身份和爱好。您可以告诉我更多关于自己,让我更好地了解您。
上面例子中使用LangChain提供的RunnableWithMessageHistory、ChatMessageHistory来自动管理聊天对话,并通过实现一个get_session_history函数来实现在不同session之间的对话切换。在session_id=abc123的对话中,AI记住了我的名字和爱好;当切换到session_id=abc456的对话时,这个新聊天的历史就是空的。
sqlite 数据库
存在内存中的聊天记录在程序结束之后就没有了;为了保留聊天记录,需要将历史数据存放到外部的存储中,比如文件、数据库等。LangChain提供了不同的组件加以支持。下面是用嵌入式数据库sqlite作为聊天记录存储的例子:
from langchain_community.chat_message_histories import SQLChatMessageHistory
chat_message_history = SQLChatMessageHistory(
session_id="test_session_id", connection="sqlite:///chat_history.db"
)
chat_message_history.add_user_message("Hello")
chat_message_history.add_ai_message("Hi")
chat_message_history.messages
[HumanMessage(content='Hello'), AIMessage(content='Hi')]
可以通过下面的函数来获取并打印某个session的聊天历史:
def get_session_history_from_sqlite(session_id: str) -> BaseChatMessageHistory:
return SQLChatMessageHistory(
session_id=session_id, connection_string="sqlite:///chat_history.db"
)
def print_session_history_from_sqlite(session_id: str) -> None:
text = f"Message History (session_id={session_id})"
print(f"\n----{text}----")
print(get_session_history_from_sqlite(session_id))
print(f"----{'-' * len(text)}----")
print_session_history_from_sqlite('test_session_id')
----Message History (session_id=test_session_id)----
Human: Hello
AI: Hi
----------------------------------------------------
prompt = ChatPromptTemplate.from_messages(
[
("system", "You are a helpful assistant."),
MessagesPlaceholder(variable_name="history"),
("human", "{question}"),
]
)
chain = prompt | ChatOpenAI()
chain_with_history = RunnableWithMessageHistory(
chain,
lambda session_id: SQLChatMessageHistory(
session_id=session_id, connection_string="sqlite:///chat_history.db"
),
input_messages_key="question",
history_messages_key="history",
)
session_id = "chat_his_1"
config = {"configurable": {"session_id": session_id}}
print_session_history_from_sqlite(session_id)
result = chain_with_history.invoke({"question": "Hi! 你好,我是Andy,我喜欢编程和写作"}, config=config)
print(f"AI: {result.content}")
print_session_history_from_sqlite(session_id)
result = chain_with_history.invoke({"question": "81÷9=?"}, config=config)
print(f"AI: {result.content}")
print_session_history_from_sqlite(session_id)
result = chain_with_history.invoke({"question": "你还记得我吗?"}, config=config)
print(f"AI: {result.content}")
----Message History (session_id=chat_his_1)----
-----------------------------------------------
AI: 你好 Andy!很高兴认识你。编程和写作都是很有趣的爱好!你有任何关于编程或写作方面的问题需要帮助吗?我会尽力回答你的问题。
----Message History (session_id=chat_his_1)----
Human: Hi! 你好,我是Andy,我喜欢编程和写作
AI: 你好 Andy!很高兴认识你。编程和写作都是很有趣的爱好!你有任何关于编程或写作方面的问题需要帮助吗?我会尽力回答你的问题。
-----------------------------------------------
AI: 81 ÷ 9 = 9.
----Message History (session_id=chat_his_1)----
Human: Hi! 你好,我是Andy,我喜欢编程和写作
AI: 你好 Andy!很高兴认识你。编程和写作都是很有趣的爱好!你有任何关于编程或写作方面的问题需要帮助吗?我会尽力回答你的问题。
Human: 81÷9=?
AI: 81 ÷ 9 = 9.
-----------------------------------------------
AI: 是的,我记得你是Andy,喜欢编程和写作。有什么我可以帮忙的吗?
我们可以通过sqliteonline、sqlime等网站在线查看写入到chat_history.db中的聊天记录数据。下图是通过sqlime查看的结果:
可见,聊天结果是写入到数据表message_store中,其中有id、session_id和message等三个字段。
其他存储
除了sqlite,LangChain还提供了各种Memory类供存储聊天历史,常见的包括:
- Google Firestore
- Postgres
- Redis
- MongoDB
- Neo4j
- 。。。
你可以根据自己的需要,自行选定。
用Memory来管理聊天历史的系统框图,如下:
其中的Runnable部分,与之前的ChatOpenAI略有不同,是一个如下的代码片段:
prompt = ChatPromptTemplate.from_messages(
[
("system", "You are a helpful assistant."),
MessagesPlaceholder(variable_name="history"),
("human", "{question}"),
]
)
chain = prompt | ChatOpenAI()
chain_with_history = RunnableWithMessageHistory(
chain,
lambda session_id: SQLChatMessageHistory(
session_id=session_id, connection_string="sqlite:///chat_history.db"
),
input_messages_key="question",
history_messages_key="history",
)
这就引出来接下来要探索的ChatPromptTemplate和LangChain Expression Language (LCEL) 。
【未完待续】