From ab3e88bba496d92f14e097a9de96e73c5924cc46 Mon Sep 17 00:00:00 2001 From: msb_31955 Date: Tue, 24 Feb 2026 19:40:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A8=A1=E6=8B=9F=E6=9C=BA=E5=99=A8=E4=BA=BA?= =?UTF-8?q?=201=E3=80=81=E5=AE=8C=E6=88=90=E8=81=8A=E5=A4=A9=E4=B8=8A?= =?UTF-8?q?=E4=B8=8B=E6=96=87=E4=BF=9D=E5=AD=98=E3=80=82=202=E3=80=81?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=B8=8A=E4=B8=8B=E6=96=87=E5=9F=BA=E4=BA=8E?= =?UTF-8?q?sqlite=E4=BF=9D=E5=AD=98=E3=80=82=203=E3=80=81=E5=89=AA?= =?UTF-8?q?=E8=BE=91=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95=E5=92=8C=E6=91=98?= =?UTF-8?q?=E8=A6=81=E4=BF=A1=E6=81=AF=EF=BC=88=E5=8E=86=E5=8F=B2=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E6=B8=85=E7=A9=BA=EF=BC=8C=E4=BF=9D=E7=95=99=E6=9C=80?= =?UTF-8?q?=E8=BF=91=E4=B8=A4=E6=9D=A1=EF=BC=89=204=E3=80=81=E4=BF=9D?= =?UTF-8?q?=E7=95=99=E6=89=80=E6=9C=89=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=EF=BC=8Cllm=E9=87=8C=E4=BC=A0=E5=85=A5=E6=9C=80=E8=BF=91?= =?UTF-8?q?=E4=B8=A4=E6=9D=A1=E8=B7=9F=E6=89=80=E6=9C=89=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=9A=84=E8=AE=B0=E5=BD=95=E3=80=82=205=E3=80=81=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E6=A8=A1=E6=8B=9F=E6=9C=BA=E5=99=A8=E4=BA=BA=E7=9A=84?= =?UTF-8?q?Web=E7=95=8C=E9=9D=A2=E5=92=8C=E5=85=B7=E4=BD=93=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../05-聊天机器人的Web界面.py | 25 +++ ...Web界面跟LLM结合实现页面访问.py | 156 ++++++++++++++++ .../07-实现语音转文字.py | 176 ++++++++++++++++++ 3 files changed, 357 insertions(+) create mode 100644 04-多模态机器人案例/05-聊天机器人的Web界面.py create mode 100644 04-多模态机器人案例/06-Web界面跟LLM结合实现页面访问.py create mode 100644 04-多模态机器人案例/07-实现语音转文字.py diff --git a/04-多模态机器人案例/05-聊天机器人的Web界面.py b/04-多模态机器人案例/05-聊天机器人的Web界面.py new file mode 100644 index 0000000..15ec768 --- /dev/null +++ b/04-多模态机器人案例/05-聊天机器人的Web界面.py @@ -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() # 界面跑起来 \ No newline at end of file diff --git a/04-多模态机器人案例/06-Web界面跟LLM结合实现页面访问.py b/04-多模态机器人案例/06-Web界面跟LLM结合实现页面访问.py new file mode 100644 index 0000000..7a9f990 --- /dev/null +++ b/04-多模态机器人案例/06-Web界面跟LLM结合实现页面访问.py @@ -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这个聊天列表(是个数组,里面是一条一条消息,包括AiMessage,toolmessage,humanmessage,systemmessage。。) + 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_summarize,last_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_messages,summary +# 然后再第二个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() # 界面跑起来 diff --git a/04-多模态机器人案例/07-实现语音转文字.py b/04-多模态机器人案例/07-实现语音转文字.py new file mode 100644 index 0000000..df1d87f --- /dev/null +++ b/04-多模态机器人案例/07-实现语音转文字.py @@ -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这个聊天列表(是个数组,里面是一条一条消息,包括AiMessage,toolmessage,humanmessage,systemmessage。。) + 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_summarize,last_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_messages,summary +# 然后再第二个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() # 界面跑起来