书接上文,本文继续介绍自己的LLM学习之路,构建一个简单的chatbot!本文直接介绍项目流程,不再说明api和环境等问题,想了解可以看之前的文章。使用 LCEL 构建简单的LLM应用程序-CSDN博客
直接开始可能还是略显复杂,本文参照LangChain官网一步一步说明。
第一步
当然是创建chat实例。仍然以讯飞星火大模型为例。
SPARKAI_URL = 'wss://spark-api.xf-yun.com/v3.5/chat'
SPARKAI_APP_ID = ''
SPARKAI_API_SECRET = ''
SPARKAI_API_KEY = ''
SPARKAI_DOMAIN = 'generalv3.5'
model = ChatSparkLLM(
spark_app_id=SPARKAI_APP_ID, spark_api_key=SPARKAI_API_KEY, spark_api_secret=SPARKAI_API_SECRET
)
# 三个例子表明没有session_id时只会处理一句输入不会理解上下文
model.invoke([HumanMessage(content="Hi! I'm Bob")])
print(model.invoke([HumanMessage(content="Hi! I'm Bob")]))
model.invoke([HumanMessage(content="What's my name?")])
print(model.invoke([HumanMessage(content="What's my name?")]))
print(model.invoke([HumanMessage(content="你可以做什么")]))
给了三个例子说明,如果没有后续的处理我们使用大模型回答我们的问题只是一句一句的,不会读取历史信息。
第二步
定义config给session_id赋值,这样就可以找到对应任务的上文。
# 如何处理上下文的信息呢?
store = {}
# 至关重要的get_session_history
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
print(session_id) # 我自己加的打印便于说明session_id
return store[session_id]
with_message_history = RunnableWithMessageHistory(model, get_session_history)
# 定义不同的config来表示不同的任务id
config = {"configurable": {"session_id": "abc2"}}
config1 = {"configurable": {"session_id": "abc3"}}
response = with_message_history.invoke(
[HumanMessage(content="Hi! I'm Bob")],
config=config,
)
response = with_message_history.invoke(
[HumanMessage(content="What's my name?")],
config=config,
)
print(response.content)
如上所示,如果我们都使用config,session_id都为abc2,得到的输出就会告诉我们名字是Bob。
response = with_message_history.invoke(
[HumanMessage(content="What's my name?")],
config=config1,
)
如果使用config1的话,session_id不一致则会说明我不知道你的名字,读者可以自己试试。
以上是对我们怎么处理可以得到历史消息,接下来是复杂一点的chatbot的创建。
第一步
参考之前的chat的创建
第二步
prompt
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"), # MessagesPlaceholder对象通常用于定义一个模板中的变量,这样就可以在运行时动态地替换实际的消息内容,根据不同的输入动态生成输出
]
)
这里要特别说明这个MessagesPlaceholder,这个对象可以让我们输入的messages为动态的,我的输入可以是上一篇文章的{"language": "italian", "text": "hi"},也可以是 [HumanMessage(content="hi! I'm bob")],可能我没讲太明白大家可以直接去搜索。
第三步
定义一个“修剪器”trimmer
trimmer = trim_messages(
max_tokens=65,
strategy="last",
token_counter=model,
include_system=True,
allow_partial=False,
start_on="human",
)
# 历史消息记录
messages = [
SystemMessage(content="you're a good assistant"),
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!"),
]
trimmer.invoke(messages)
trimmer
的功能是基于配置的规则来裁剪消息,以满足token数量的限制,而不是用来管理或清除历史消息记录。一定要有trimmer.invoke(messages)才会对消息进行裁剪!
第四步
链在一起
chain = (
RunnablePassthrough.assign(messages=itemgetter("messages") | trimmer)
| prompt
| model
)
第五步
小测试说明可以获得历史信息
response = chain.invoke(
{
"messages": messages + [HumanMessage(content="what's my name?")],
"language": "English",
}
)
print(response.content)
第六步
定义get_session_history并说明trimmer的作用
# 如何处理上下文的信息呢?
store = {}
# 至关重要的get_session_history
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
print(session_id) # 我自己加的打印便于说明session_id
return store[session_id]
with_message_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="messages",
)
config = {"configurable": {"session_id": "abc20"}}
response = with_message_history.invoke(
{
"messages": messages + [HumanMessage(content="whats my name?")],
"language": "English",
},
config=config,
)
print(response.content)
此时我们再次问我的名字
输出如上所示,显示我不知道你的名字。因为之前说明名字的历史信息已经被trimmer裁剪掉了。trimmer是为了控制对话的长度和内容,确保对话质量同时满足特定的要求或限制。
第七步
流的简单说明
config15 = {"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=config15,
):
print(r.content, end="|")
只需要调用.stream即可实现。输出就会有进度显示来增强交互性。
以上就是各个步骤的说明,只是一个很简单的chatbot的实现。下面附上整个项目代码。
from fastapi import FastAPI
from langchain_core.output_parsers import StrOutputParser
from langchain_community.chat_models import ChatSparkLLM
from langchain_core.messages import HumanMessage, AIMessage
from langserve import add_routes
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.chat_history import (
BaseChatMessageHistory,
InMemoryChatMessageHistory,
)
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.messages import SystemMessage, trim_messages
from operator import itemgetter
from langchain_core.runnables import RunnablePassthrough
# Create model
SPARKAI_URL = 'wss://spark-api.xf-yun.com/v3.5/chat'
SPARKAI_APP_ID = '7275269b'
SPARKAI_API_SECRET = 'ZmQ0NTRjYWYxZjExMjNiODY1ZGU1ZGQ3'
SPARKAI_API_KEY = '4794036911ddff294d676d6567f1daac'
SPARKAI_DOMAIN = 'generalv3.5'
model = ChatSparkLLM(
spark_app_id=SPARKAI_APP_ID, spark_api_key=SPARKAI_API_KEY, spark_api_secret=SPARKAI_API_SECRET
)
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"), # MessagesPlaceholder对象通常用于定义一个模板中的变量,这样就可以在运行时动态地替换实际的消息内容,根据不同的输入动态生成输出
]
)
trimmer = trim_messages(
max_tokens=65,
strategy="last",
token_counter=model,
include_system=True,
allow_partial=False,
start_on="human",
)
# 历史消息记录
messages = [
SystemMessage(content="you're a good assistant"),
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!"),
]
trimmer.invoke(messages)
chain = (
RunnablePassthrough.assign(messages=itemgetter("messages") | trimmer)
| prompt
| model
)
response = chain.invoke(
{
"messages": messages + [HumanMessage(content="what's my name?")],
"language": "English",
}
)
print(response.content)
response = chain.invoke(
{
"messages": messages + [HumanMessage(content="what problem did i ask")],
"language": "English",
}
)
print(response.content)
# 如何处理上下文的信息呢?
store = {}
# 至关重要的get_session_history
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
print(session_id) # 我自己加的打印便于说明session_id
return store[session_id]
with_message_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="messages",
)
config = {"configurable": {"session_id": "abc20"}}
response = with_message_history.invoke(
{
"messages": messages + [HumanMessage(content="whats my name?")],
"language": "English",
},
config=config,
)
print(messages)
print(response.content)
config15 = {"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=config15,
):
print(r.content, end="|")
欢迎大佬指正!