模拟机器人

1、完成聊天上下文保存。
2、完成上下文基于sqlite保存。
3、剪辑历史记录和摘要信息(历史记录清空,保留最近两条)
4、保留所有历史记录,llm里传入最近两条跟所有数据的记录。
5、完成模拟机器人的Web界面和具体功能
master
msb_31955 5 months ago
parent d42b4dee4a
commit ab3e88bba4

@ -0,0 +1,25 @@
import gradio as gr;
# 1.开发一个聊天机器人的Web界面 gr.Blocks
# title:标题theme=gr.themes.Soft() 界面默认样式
with gr.Blocks(title='多模态聊天机器人', theme=gr.themes.Soft()) as block:
# 2.加入聊天历史记录的组件 Chatbot
# 高版本的gradio移除了type
chatbot = gr.Chatbot(height=500, label='聊天机器人')
# 3. 行组件,
with gr.Row():
#gr.Row() 表示 “行”gr.Column() 表示 “列”,结合 scale 参数就能实现你说的左右分布,且列的宽度比例是 4:1。我会用新手能听懂的方式讲清楚每一部分的含义和效果。
# 行组件中左面:文字输入的区域
with gr.Column(scale=4):
user_input = gr.Textbox(placeholder='请给机器人发送消息...', label='文字输入', max_lines=5)
submit_btn = gr.Button('发送', variant="primary")
# 行组件中右面语音输入区域type='filepath'录制音频的文件后缀wav你用mp3也可以
with gr.Column(scale=1):
audio_input = gr.Audio(sources=['microphone'], label='语音输入', type='filepath', format='wav')
if __name__ == '__main__':
block.launch() # 界面跑起来

@ -0,0 +1,156 @@
from langchain_community.chat_message_histories import SQLChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableWithMessageHistory, RunnablePassthrough
from langchain_openai import ChatOpenAI
import gradio as gr;
from env_util import DASHSCOPE_API_KEY, DASHSCOPE_BASE_URL
# 0、llm~~
llm = ChatOpenAI(
model = "qwen-plus",
base_url=DASHSCOPE_BASE_URL,
api_key=DASHSCOPE_API_KEY,
temperature=0.8,
);
# ===============================================================================================
# 1、定义专门做聊天的提示词模板
prompt = ChatPromptTemplate.from_messages([
('system', "{system_message}"), # 系统提示词
MessagesPlaceholder(variable_name='chat_history', optional=True), #消息占位符
('human', '{input}') #用户提示词input用户传的问题
])
chain = prompt | llm;
# ===============================================================================================
# 2、存储聊天记录 存的谁存的第10行的内容存到哪里内存、关系型数据库或者redis数据库
store = {}
def get_session_history(session_id: str):
"""从关系型数据库的历史消息列表中 返回当前会话 的所有历史消息"""
# SQLChatMessageHistory是langchain提供的
return SQLChatMessageHistory(
session_id=session_id,
# 这里url换为自己的数据库即可
connection_string='sqlite:///chat_history.db',
)
# ===============================================================================================
# 3、创建带历史记录功能的处理链帮我自动存储历史记录
chain_with_message_history = RunnableWithMessageHistory(
chain, # 基础执行链
get_session_history, # 指定工厂函数返回指定session_id的聊天记录
input_messages_key='input', # 指定用户输入的消息的key
history_messages_key='chat_history', # 历史消息记录的key
)
# ===============================================================================================
#4、剪辑和摘要历史上下文消息
# 比如保留最近的前2条消息随意指定条数把之前的所有消息形成摘要
# 定义summarize_messages函数把当前用户的这次输入传进来
def summarize_messages(current_input):
"""剪辑和摘要上下午,历史记录"""
# 从current_input取出session_id
session_id = current_input['config']["configurable"]["session_id"]
if not session_id:
raise ValueError("必须通过config参数提供session_id")
# 获取当前会话ID的所有历史聊天记录
chat_history = get_session_history(session_id)
# 从历史聊天记录取出message这个聊天列表是个数组里面是一条一条消息包括AiMessagetoolmessagehumanmessagesystemmessage。。
stored_messages = chat_history.messages
# 如果stored_messages长度太短只有两条以内聊天记录不需要摘要
if len(stored_messages) <= 2: # 保留最近2条消息的阈值
return {"original_messages": stored_messages, "summary": None} # 不满足摘要条件时返回原始消息-保留的最后2条消息
# 超过2条再剪辑
# 剪辑消息列表
last_two_messages = stored_messages[-2:] # 保留的最后2条消息
messages_to_summarize = stored_messages[:-2] # 需要进行摘要的消息列表最后2条之前的记录
# 构建摘要:就是让大模型去帮你构建摘要,帮你做这件事情,在这里需要再调用大模型
# 所以先构建提示词模版
summarization_prompt = ChatPromptTemplate.from_messages([
("system", "请将以下对话历史压缩为一条保留关键信息的摘要消息。"),
("placeholder", "{chat_history}"),
("human", "请生成包含上述对话核心内容的摘要,保留重要事实和决策。")
])
# 组成链调用大模型
summarization_chain = summarization_prompt | llm
# 大模型会帮你生成摘要(AIMessage) ——》需要生成摘要的是messages_to_summarizelast_two_messages不需要生成摘要
summary_message = summarization_chain.invoke({'chat_history': messages_to_summarize})
# 返回结构化结果不调用chat_history.clear()
return {
"original_messages": last_two_messages, # 保留的最后2条消息
"summary": summary_message # 需要进行摘要的消息列表最后2条之前的记录
}
# 5、最终的链 需要LCEL完成
# RunnablePassthrough 默认会将输入数据原样传递到下游通过管道传给下一个组件RunnablePassthrough
# 在第二个RunnablePassthrough收到了前面返回得两个key分别是original_messagessummary
# 然后再第二个RunnablePassthrough分别把内容注入到chat_history以及修改的提示词模板的system里system也换成了占位符这里相当于赋值
final_chain = RunnablePassthrough.assign(messages_summarized=summarize_messages) | RunnablePassthrough.assign(
input = lambda x: x['input'],
chat_history = lambda x: x['messages_summarized']['original_messages'],
system_message = lambda x: f"你是一个乐于助人的助手。尽你所能回答所有问题。摘要:{x ['messages_summarized']['summary']}" if x['messages_summarized'].get('summary') else "",
) | chain_with_message_history;
# ===============================================================================================
#6、 启动Web页面实现内容交互
# web界面中的核心函数
# 第一个参数:聊天记录,第二个参数:用户输入的消息
# 这个功能只是把用户输入的消息添加到历史记录中,并没有调用大模型
def add_message(chat_history, user_message):
if user_message:
# 角色:用户角色
chat_history.append({"role": "user", "content": user_message})
return chat_history, ''
# 调用大模型
def execute_chain(chat_history):
# 拿最后一条记录,最后一条就是我们刚才在文本框输入的内容
input = chat_history[-1]
# 调用模型
result = final_chain.invoke({'input': input['content'], "config": {"configurable": {"session_id": "user123"}}},
config={"configurable": {"session_id": "user123"}})
# 模型返回结果追加到历史记录
chat_history.append({'role': 'assistant', 'content': result.content})
return chat_history
# 1.开发一个聊天机器人的Web界面 gr.Blocks
# title:标题theme=gr.themes.Soft() 界面默认样式
with gr.Blocks(title='多模态聊天机器人', theme=gr.themes.Soft()) as block:
# 2.加入聊天历史记录的组件 Chatbot
chatbot = gr.Chatbot(height=500, label='聊天机器人')
# 3. 行组件,
with gr.Row():
# gr.Row() 表示 “行”gr.Column() 表示 “列”,结合 scale 参数就能实现你说的左右分布,且列的宽度比例是 4:1。我会用新手能听懂的方式讲清楚每一部分的含义和效果。
# 行组件中左面:文字输入的区域
with gr.Column(scale=4):
user_input = gr.Textbox(placeholder='请给机器人发送消息...', label='文字输入', max_lines=5)
submit_btn = gr.Button('发送', variant="primary")
# 行组件中右面语音输入区域type='filepath'录制音频的文件后缀wav你用mp3也可以
with gr.Column(scale=1):
audio_input = gr.Audio(sources=['microphone'], label='语音输入', type='filepath', format='wav')
# 添加提交事件(要调用的函数,函数传入的组件列表,函数输出的组件列表)
chat_msg = user_input.submit(add_message, [chatbot, user_input], [chatbot, user_input])
# 再去调用大模型
chat_msg.then(execute_chain, chatbot, chatbot)
if __name__ == '__main__':
block.launch() # 界面跑起来

@ -0,0 +1,176 @@
from langchain_community.chat_message_histories import SQLChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableWithMessageHistory, RunnablePassthrough
from langchain_openai import ChatOpenAI
import gradio as gr;
from zhipuai import ZhipuAI;
from env_util import DASHSCOPE_API_KEY, DASHSCOPE_BASE_URL ,ZHIPU_API_KEY;
# 0、llm~~
llm = ChatOpenAI(
model = "qwen-plus",
base_url=DASHSCOPE_BASE_URL,
api_key=DASHSCOPE_API_KEY,
temperature=0.8,
);
# ===============================================================================================
# 1、定义专门做聊天的提示词模板
prompt = ChatPromptTemplate.from_messages([
('system', "{system_message}"), # 系统提示词
MessagesPlaceholder(variable_name='chat_history', optional=True), #消息占位符
('human', '{input}') #用户提示词input用户传的问题
])
chain = prompt | llm;
# ===============================================================================================
# 2、存储聊天记录 存的谁存的第10行的内容存到哪里内存、关系型数据库或者redis数据库
store = {}
def get_session_history(session_id: str):
"""从关系型数据库的历史消息列表中 返回当前会话 的所有历史消息"""
# SQLChatMessageHistory是langchain提供的
return SQLChatMessageHistory(
session_id=session_id,
# 这里url换为自己的数据库即可
connection_string='sqlite:///chat_history.db',
)
# ===============================================================================================
# 3、创建带历史记录功能的处理链帮我自动存储历史记录
chain_with_message_history = RunnableWithMessageHistory(
chain, # 基础执行链
get_session_history, # 指定工厂函数返回指定session_id的聊天记录
input_messages_key='input', # 指定用户输入的消息的key
history_messages_key='chat_history', # 历史消息记录的key
)
# ===============================================================================================
#4、剪辑和摘要历史上下文消息
# 比如保留最近的前2条消息随意指定条数把之前的所有消息形成摘要
# 定义summarize_messages函数把当前用户的这次输入传进来
def summarize_messages(current_input):
"""剪辑和摘要上下午,历史记录"""
# 从current_input取出session_id
session_id = current_input['config']["configurable"]["session_id"]
if not session_id:
raise ValueError("必须通过config参数提供session_id")
# 获取当前会话ID的所有历史聊天记录
chat_history = get_session_history(session_id)
# 从历史聊天记录取出message这个聊天列表是个数组里面是一条一条消息包括AiMessagetoolmessagehumanmessagesystemmessage。。
stored_messages = chat_history.messages
# 如果stored_messages长度太短只有两条以内聊天记录不需要摘要
if len(stored_messages) <= 2: # 保留最近2条消息的阈值
return {"original_messages": stored_messages, "summary": None} # 不满足摘要条件时返回原始消息-保留的最后2条消息
# 超过2条再剪辑
# 剪辑消息列表
last_two_messages = stored_messages[-2:] # 保留的最后2条消息
messages_to_summarize = stored_messages[:-2] # 需要进行摘要的消息列表最后2条之前的记录
# 构建摘要:就是让大模型去帮你构建摘要,帮你做这件事情,在这里需要再调用大模型
# 所以先构建提示词模版
summarization_prompt = ChatPromptTemplate.from_messages([
("system", "请将以下对话历史压缩为一条保留关键信息的摘要消息。"),
("placeholder", "{chat_history}"),
("human", "请生成包含上述对话核心内容的摘要,保留重要事实和决策。")
])
# 组成链调用大模型
summarization_chain = summarization_prompt | llm
# 大模型会帮你生成摘要(AIMessage) ——》需要生成摘要的是messages_to_summarizelast_two_messages不需要生成摘要
summary_message = summarization_chain.invoke({'chat_history': messages_to_summarize})
# 返回结构化结果不调用chat_history.clear()
return {
"original_messages": last_two_messages, # 保留的最后2条消息
"summary": summary_message # 需要进行摘要的消息列表最后2条之前的记录
}
# 5、最终的链 需要LCEL完成
# RunnablePassthrough 默认会将输入数据原样传递到下游通过管道传给下一个组件RunnablePassthrough
# 在第二个RunnablePassthrough收到了前面返回得两个key分别是original_messagessummary
# 然后再第二个RunnablePassthrough分别把内容注入到chat_history以及修改的提示词模板的system里system也换成了占位符这里相当于赋值
final_chain = RunnablePassthrough.assign(messages_summarized=summarize_messages) | RunnablePassthrough.assign(
input = lambda x: x['input'],
chat_history = lambda x: x['messages_summarized']['original_messages'],
system_message = lambda x: f"你是一个乐于助人的助手。尽你所能回答所有问题。摘要:{x ['messages_summarized']['summary']}" if x['messages_summarized'].get('summary') else "",
) | chain_with_message_history;
# ===============================================================================================
# web界面中的核心函数
# 第一个参数:聊天记录,第二个参数:用户输入的消息
# 这个功能只是把用户输入的消息添加到历史记录中,并没有调用大模型
def add_message(chat_history, user_message):
if user_message:
# 角色:用户角色
chat_history.append({"role": "user", "content": user_message})
return chat_history, ''
# 调用大模型
def execute_chain(chat_history):
# 拿最后一条记录,最后一条就是我们刚才在文本框输入的内容
input = chat_history[-1]
# 调用模型
result = final_chain.invoke({'input': input['content'], "config": {"configurable": {"session_id": "user123"}}},
config={"configurable": {"session_id": "user123"}})
# 模型返回结果追加到历史记录
chat_history.append({'role': 'assistant', 'content': result.content})
return chat_history
# 定义函数:读取音频文件
def read_audio(audio_message):
"""读取音频文件"""
print(audio_message) # 输出音频文件的路径,会在你的电脑中,可本地播放
if audio_message:
client = ZhipuAI(api_key=ZHIPU_API_KEY) # 填写您自己的APIKey
with open(audio_message, "rb") as audio_file:
resp = client.audio.transcriptions.create(
model="glm-asr",
file=audio_file,
stream=False
)
text = resp.model_extra['text']
return text
return ''
# 1.开发一个聊天机器人的Web界面 gr.Blocks
# title:标题theme=gr.themes.Soft() 界面默认样式
with gr.Blocks(title='多模态聊天机器人', theme=gr.themes.Soft()) as block:
# 2.加入聊天历史记录的组件 Chatbot
chatbot = gr.Chatbot(type='messages', height=500, label='聊天机器人')
# 3. 行组件,
with gr.Row():
# gr.Row() 表示 “行”gr.Column() 表示 “列”,结合 scale 参数就能实现你说的左右分布,且列的宽度比例是 4:1。我会用新手能听懂的方式讲清楚每一部分的含义和效果。
# 行组件中左面:文字输入的区域
with gr.Column(scale=4):
user_input = gr.Textbox(placeholder='请给机器人发送消息...', label='文字输入', max_lines=5)
submit_btn = gr.Button('发送', variant="primary")
# 行组件中右面语音输入区域type='filepath'录制音频的文件后缀wav你用mp3也可以
with gr.Column(scale=1):
audio_input = gr.Audio(sources=['microphone'], label='语音输入', type='filepath', format='wav')
# 添加提交事件(要调用的函数,函数传入的组件列表,函数输出的组件列表)
chat_msg = user_input.submit(add_message, [chatbot, user_input], [chatbot, user_input])
# 再去调用大模型
chat_msg.then(execute_chain, chatbot, chatbot)
# 添加语音输入框的改变时间(语音输入后就会改变,改变后就触发)
# 语音输入框的改变事件
audio_input.change(read_audio, [audio_input], [user_input])
if __name__ == '__main__':
block.launch() # 界面跑起来
Loading…
Cancel
Save