AI大模型工程师-应用篇 系列目录:
├── LangChain
│ ├── 提示词模板、对话管理与结构化输出核心用法
│ ├── LCEL 表达式语法
│ └── 多模态聊天机器人实战(本文)
├── Embedding
│ └── Embedding 与向量数据库简单应用——从文本向量化到 RAG 检索增强生成
├── LangGraph
│ └── LangGraph 工作流与 Agent 开发实战
└── MCP
  └── 协议与 Agent 通信实战(即将发布)

本文从零开始,逐步构建一个支持文本、音频、图像多模态输入的聊天机器人。在实战过程中,将综合运用 LangChain 的对话记忆管理(InMemory / SQLite)、历史摘要策略、LCEL 链式编排,并结合 Gradio 搭建交互式 Web 界面,完整展示一个 AI 应用从后端逻辑到前端交互的全链路开发过程。

一、项目概述与依赖准备

1.1 我们要构建什么

本文将分步构建一个多模态聊天机器人,最终形态支持:

  • 文本对话:基础问答 + 多轮上下文记忆
  • 对话持久化:聊天记录存储到 SQLite 数据库,重启不丢失
  • 历史摘要:自动压缩过长的对话历史,避免 Token 溢出
  • 语音输入:通过 ASR 语音识别将语音转为文字
  • 图像 / 音频原生理解:借助多模态大模型,直接处理图片和音频

整体技术栈:

层级技术选型
链路编排LangChain LCEL
对话记忆RunnableWithMessageHistory + SQLChatMessageHistory
历史管理RunnablePassthrough.assign + 摘要链
Web 界面Gradio
语音识别ZhipuAI ASR(glm-asr)
多模态处理Base64 编码 + 多模态 LLM

1.2 额外依赖

本文使用 uv 作为 Python 包管理工具。将以下内容保存为项目根目录下的 pyproject.toml 文件,然后在终端执行 uv sync,即可自动创建虚拟环境并安装所有依赖:

[project]
name = "langchainproject"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
    "dotenv>=0.9.9",
    "gradio>=6.8.0",              # Web 界面框架
    "langchain>=1.2.10",
    "langchain-community>=0.4.1",
    "langchain-openai>=1.1.10",
    "openai>=2.24.0",
    "zhipuai>=2.1.5.20250825",    # 智谱 AI SDK(用于 ASR 语音识别)
]

1.3 模型准备

本文涉及两类模型,均已在第一篇文章的 models.py 中配置:

from models import llm                # 文本对话模型(如本地 qwen3)
from models import get_multimodal_llm # 多模态模型(如 qwen-omni-3b)

二、对话记忆基础——RunnableWithMessageHistory

多轮对话的核心问题在于:大模型本身是无状态的,每次调用都是一次独立的请求。要让模型"记住"之前的对话内容,必须在每次请求时将历史消息一并传入。

LangChain 的 RunnableWithMessageHistory 封装了这一逻辑——它会在调用链路前自动读取历史消息,调用后自动保存新消息,开发者无需手动管理。

2.1 内存存储:InMemoryChatMessageHistory

最简单的方案是将历史消息 保存在进程内存 中。

from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableWithMessageHistory
from models import llm

# Step 1:定义提示词模板
prompt = ChatPromptTemplate.from_messages([
    ('system', '你是一个乐于助人的助手。尽你所能回答所有问题。'),
    MessagesPlaceholder(variable_name='chat_history', optional=True),
    ('human', '{input}')
])

chain = prompt | llm

# Step 2:基于内存的会话存储
store = {}  # key: session_id, value: InMemoryChatMessageHistory

def get_session_history(session_id: str):
    """根据会话 ID 获取对应的历史记录"""
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]

# Step 3:包装为带记忆的链路
chain_with_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key='input',          # 输入字典中,哪个 key 是当前用户消息
    history_messages_key='chat_history',  # 模板中,哪个占位符接收历史消息
)

# Step 4:多轮对话测试
config = {"configurable": {"session_id": "user_001"}}

res1 = chain_with_history.invoke({'input': '你好,我的工号是 A1024'}, config=config)
print(res1.content)

res2 = chain_with_history.invoke({'input': '我的工号是多少?'}, config=config)
print(res2.content)  # 模型能回忆起工号 A1024

关键参数说明:

参数作用
input_messages_key指定输入字典中当前用户消息的 key
history_messages_key指定提示词模板中接收历史消息的占位符名称
session_id会话隔离标识,不同 session_id 的历史互不干扰

局限性:InMemoryChatMessageHistory 存储在进程内存中,程序重启后历史记录全部丢失,仅适用于开发调试阶段。


三、持久化存储——SQLChatMessageHistory

生产环境中,聊天记录需要持久化到数据库。LangChain 内置了 SQLChatMessageHistory,支持将消息存储到关系型数据库(SQLite、PostgreSQL、MySQL 等)。

3.1 从内存切换到 SQLite

只需替换 get_session_history 函数的实现,链路的其他部分完全不变:

from langchain_community.chat_message_histories import SQLChatMessageHistory

def get_session_history(session_id: str):
    """从 SQLite 数据库中获取会话历史"""
    return SQLChatMessageHistory(
        session_id=session_id,
        connection_string='sqlite:///chat_history.db',
    )

替换后,消息会自动存入本地 chat_history.db 文件,程序重启后对话记录依然存在。

对比变化:

对比项InMemoryChatMessageHistorySQLChatMessageHistory
数据存储进程内存(dict)关系型数据库文件
持久性程序重启即丢失永久保存
多进程共享不支持支持(通过数据库)
手动管理需自行维护 store 字典自动管理,传入 session_id 即可

connection_string 遵循 SQLAlchemy 格式。SQLite 使用 sqlite:/// 前缀 + 文件路径;若使用 PostgreSQL,则为 postgresql://user:pass@host:port/dbname。


四、历史摘要——上下文窗口管理策略

随着对话轮次增加,历史消息会不断累积。大模型的上下文窗口是有限的,过长的历史不仅导致 Token 超限报错,还会增加推理延迟和成本。因此需要一种策略来压缩历史消息。

核心思路:保留最近的 N 条原始消息,将更早的消息交由 LLM 生成摘要。

4.1 策略一:重建历史(直接替换)

该方案在每次调用前检查历史长度,若超过阈值,则清空数据库中的旧记录,用"摘要 + 最近消息"重建历史。

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from models import llm

def summarize_messages(current_input):
    """检查并压缩过长的对话历史"""
    session_id = current_input['config']["configurable"]["session_id"]

    chat_history = get_session_history(session_id)
    stored_messages = chat_history.messages

    if len(stored_messages) <= 2:
        return False  # 消息不多,无需摘要

    # 分割:保留最近 2 条,其余用于生成摘要
    last_two = stored_messages[-2:]
    to_summarize = stored_messages[:-2]

    # 构建摘要链
    summarization_prompt = ChatPromptTemplate.from_messages([
        ("system", "请将以下对话历史压缩为一条保留关键信息的摘要消息。"),
        ("placeholder", "{chat_history}"),
        ("human", "请生成包含上述对话核心内容的摘要,保留重要事实和决策。")
    ])
    summary_chain = summarization_prompt | llm
    summary_message = summary_chain.invoke({'chat_history': to_summarize})

    # 重建历史:清空 → 写入摘要 → 写入最近消息
    chat_history.clear()
    chat_history.add_message(summary_message)
    for msg in last_two:
        chat_history.add_message(msg)

    return True

# 将摘要逻辑注入链路
final_chain = (
    RunnablePassthrough.assign(messages_summarized=summarize_messages)
    | chain_with_history
)

调用方式:

注意 config 需要同时出现在输入字典和 invoke 的 config 参数中——前者供 summarize_messages 读取 session_id,后者供 RunnableWithMessageHistory 使用。

result = final_chain.invoke(
    {'input': '你好', 'config': {"configurable": {"session_id": "user_001"}}},
    config={"configurable": {"session_id": "user_001"}}
)

数据流分析:

用户输入 {'input': '...', 'config': {...}}
    ↓
RunnablePassthrough.assign(messages_summarized=summarize_messages)
    ├── 透传原始输入
    └── 执行 summarize_messages → 检查/压缩历史 → 返回 True/False
    ↓
合并后 {'input': '...', 'config': {...}, 'messages_summarized': True}
    ↓
chain_with_history → 读取已压缩的历史 → 调用 LLM → 保存新消息

局限性:chat_history.clear() 是一个破坏性操作,会永久删除原始历史记录。如果摘要生成失败或质量不佳,无法回溯到原始对话。

4.2 策略二:动态系统消息注入(非破坏性)

为解决策略一的破坏性问题,改为将摘要注入到系统消息中,不修改数据库中的原始记录。

核心改动:

  1. 提示词模板的 system 消息改为变量 {system_message},支持动态注入摘要内容。
  2. summarize_messages 返回结构化字典(而非直接操作数据库)。
  3. 使用两阶段 RunnablePassthrough.assign 分步处理数据。
# 提示词模板:system 改为动态变量
prompt = ChatPromptTemplate.from_messages([
    ('system', "{system_message}"),
    MessagesPlaceholder(variable_name='chat_history', optional=True),
    ('human', '{input}')
])

chain = prompt | llm

chain_with_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key='input',
    history_messages_key='chat_history',
)

def summarize_messages(current_input):
    """非破坏性摘要:返回结构化结果,不修改原始历史"""
    session_id = current_input['config']["configurable"]["session_id"]
    chat_history = get_session_history(session_id)
    stored_messages = chat_history.messages

    if len(stored_messages) <= 2:
        return {"original_messages": stored_messages, "summary": None}

    last_two = stored_messages[-2:]
    to_summarize = stored_messages[:-2]

    summarization_prompt = ChatPromptTemplate.from_messages([
        ("system", "请将以下对话历史压缩为一条保留关键信息的摘要消息。"),
        ("placeholder", "{chat_history}"),
        ("human", "请生成包含上述对话核心内容的摘要,保留重要事实和决策。")
    ])
    summary_message = (summarization_prompt | llm).invoke({'chat_history': to_summarize})

    return {
        "original_messages": last_two,  # 保留的原始消息
        "summary": summary_message      # 生成的摘要(AIMessage)
    }

# 两阶段 RunnablePassthrough.assign 管线
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"你是一个乐于助人的助手。尽你所能回答所有问题。"
            f"摘要:{x['messages_summarized']['summary'].content}"
            if x['messages_summarized'].get("summary")
            else "你是一个乐于助人的助手。尽你所能回答所有问题。"
        )
    )
    | chain_with_history
)

数据流分析(两阶段 assign):

输入: {'input': '你好', 'config': {...}}
    ↓
第一阶段 assign(messages_summarized=...)
    ↓
{
  'input': '你好',
  'config': {...},
  'messages_summarized': {
      "original_messages": [msg1, msg2],
      "summary": AIMessage("用户之前讨论了...")
  }
}
    ↓
第二阶段 assign(input=..., chat_history=..., system_message=...)
    ↓
{
  'input': '你好',
  'chat_history': [msg1, msg2],         ← 仅最近 2 条
  'system_message': '...摘要:用户之前讨论了...',
  ...
}
    ↓
chain_with_history → 注入历史 + 系统消息 → 调用 LLM

两种策略对比:

对比项策略一(重建历史)策略二(动态系统消息)
是否修改原始历史是(clear + 重写)否(只读)
摘要位置作为历史消息存入数据库注入到 system 消息中
可回溯性不可逆原始记录完整保留
实现复杂度较低稍高(两阶段 assign)

五、构建 Web 界面——Gradio 集成

到目前为止,所有交互都在命令行中完成——用户需要手动编写 invoke 调用、拼接 config 参数,每次对话都要重新运行脚本。这种方式对开发调试尚可,但无法作为产品交付给普通用户。我们需要一个可视化的 Web 界面,让用户通过浏览器即可直接对话。

5.1 界面布局

import gradio as gr

with gr.Blocks(title='聊天机器人', theme=gr.themes.Soft()) as demo:
    # 聊天记录展示区
    chatbot = gr.Chatbot(height=500, label='聊天机器人')

    with gr.Row():
        # 左侧:文字输入 + 发送按钮
        with gr.Column(scale=4):
            user_input = gr.Textbox(
                placeholder='请输入消息...',
                label='文字输入',
                max_lines=5
            )
            submit_btn = gr.Button('发送', variant="primary")

        # 右侧:麦克风录音区
        with gr.Column(scale=1):
            audio_input = gr.Audio(
                sources=['microphone'],
                label='语音输入',
                type='filepath',
                format='wav'
            )

界面由三部分组成:顶部是聊天记录展示区(gr.Chatbot),底部左侧是文本输入框和发送按钮,右侧是麦克风录音区域。

5.2 事件绑定

Gradio 的事件机制采用链式调用:一个事件完成后可以通过 .then() 触发下一个事件,形成处理管线。

def add_message(chat_history, user_message):
    """将用户消息追加到聊天记录"""
    if user_message:
        chat_history.append({"role": "user", "content": user_message})
    return chat_history, gr.Textbox(value=None, interactive=False)

def execute_chain(chat_history):
    """调用 LLM 生成回复"""
    user_msg = chat_history[-1]
    result = final_chain.invoke(
        {'input': user_msg['content'], 'config': {"configurable": {"session_id": "user_001"}}},
        config={"configurable": {"session_id": "user_001"}}
    )
    chat_history.append({'role': 'assistant', 'content': result.content})
    return chat_history

# 文本提交事件链:追加消息 → 调用 LLM 生成回复
chat_msg = user_input.submit(add_message, [chatbot, user_input], [chatbot, user_input])
chat_msg.then(execute_chain, chatbot, chatbot)

# 按钮点击事件(逻辑相同)
submit_btn.click(
    add_message, [chatbot, user_input], [chatbot, user_input]
).then(execute_chain, chatbot, chatbot)

事件流:

用户按下回车 / 点击发送
    ↓
add_message() → 追加 user 消息到 chatbot,锁定输入框(防止重复提交)
    ↓ .then()
execute_chain() → 取最后一条 user 消息 → 调用 LLM → 追加 assistant 回复
    ↓
界面自动刷新,显示新消息

启动应用:

if __name__ == '__main__':
    demo.launch()

add_message 返回 gr.Textbox(value=None, interactive=False) 的作用是清空输入框并暂时禁用交互,避免用户在模型响应期间重复提交。待回复完成后,可通过 .then() 重新激活输入框。


六、语音输入——ASR 语音识别集成

目前的聊天界面只支持键盘输入,但在移动端或免提场景下,打字并不方便。为了提升交互体验,我们为界面添加语音输入功能——通过 ASR(Automatic Speech Recognition)服务将录音转为文字,再走已有的文本对话链路。

6.1 接入智谱 ASR 服务

from zhipuai import ZhipuAI
from env_utils import get_env

def read_audio(audio_path):
    """调用智谱 ASR 将音频转为文字"""
    if not audio_path:
        return ''
    client = ZhipuAI(api_key=get_env("ZHIPU_API_KEY"))
    with open(audio_path, "rb") as audio_file:
        resp = client.audio.transcriptions.create(
            model="glm-asr",
            file=audio_file,
            stream=False
        )
        return resp.model_extra['text']

6.2 绑定语音事件

# 录音完成后,自动将语音转为文字并填入输入框
audio_input.change(read_audio, [audio_input], [user_input])

交互流程:

用户通过麦克风录音
    ↓ 录音结束,触发 change 事件
read_audio() → 调用 ZhipuAI ASR → 返回识别文字
    ↓
文字自动填入输入框
    ↓ 用户点击发送
走正常的文本对话流程(add_message → execute_chain)

该方案的本质是语音转文字,最终仍以文本形式与 LLM 交互,因此不需要多模态模型。适合不具备多模态模型但需要语音输入的场景。


七、全模态融合——文本 + 音频 + 图像

前面的方案将语音先转为文字再交给文本模型处理,这属于间接方案。如果使用多模态大模型(如 qwen-omni),则可以让模型直接理解音频和图像内容,无需中间转换。

7.1 架构变化

对比项第六章(ASR 方案)第七章(原生多模态)
模型类型文本模型多模态模型
音频处理ASR 转文字 → 文本输入Base64 编码 → 直接传入模型
图像处理不支持Base64 编码 → 直接传入模型
消息格式'input': '纯文本'HumanMessage(content=[多类型列表])

核心变化:HumanMessage 的 content 字段从简单字符串变为混合类型的内容列表,可同时包含文本、音频和图像。

7.2 多模态提示词模板

由于多模态输入需要构造 HumanMessage 对象(包含 content 列表),无法使用简单的 ('human', '{input}') 占位符。改为使用 MessagesPlaceholder 直接注入消息对象:

from langchain_core.messages import HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableWithMessageHistory
from models import get_multimodal_llm

multimodal_llm = get_multimodal_llm()

prompt = ChatPromptTemplate.from_messages([
    ('system', "你是一个多模态 AI 助手,可以处理文本、音频和图像输入"),
    MessagesPlaceholder(variable_name="messages"),
])

chain = prompt | multimodal_llm

# 多模态链路的历史管理
chain_with_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
)

注意此处 RunnableWithMessageHistory 未指定 input_messages_key 和 history_messages_key,因为输入直接以 HumanMessage 对象的形式传入 messages 字段,历史消息会自动注入到该占位符中。

7.3 音频处理:Base64 编码

将本地音频文件编码为 Base64 字符串,封装为模型可识别的 audio_url 消息格式。

import base64

def transcribe_audio(audio_path):
    """将音频文件编码为 Base64 并封装为消息格式"""
    try:
        with open(audio_path, 'rb') as f:
            audio_data = base64.b64encode(f.read()).decode('utf-8')
        return {
            "type": "audio_url",
            "audio_url": {
                "url": f"data:audio/wav;base64,{audio_data}",
                "duration": 30  # 单位:秒,帮助模型优化处理
            }
        }
    except Exception as e:
        print(e)
        return {}

多模态模型通常支持两种传参方式:Base64 编码(适合本地文件)和网络 URL(适合已部署到服务器的资源)。本文使用 Base64 方式。

7.4 图像处理:PIL + Base64

使用 Pillow 读取图片并保留原始格式,避免格式转换导致信息丢失(如 PNG 的透明通道)。

import io
from PIL import Image

def transcribe_image(image_path):
    """将图片转为 Base64 编码的 data URL"""
    with Image.open(image_path) as img:
        img_format = img.format if img.format else 'JPEG'
        buffered = io.BytesIO()
        img.save(buffered, format=img_format)
        image_data = base64.b64encode(buffered.getvalue()).decode('utf-8')
        return {
            "type": "image_url",
            "image_url": {
                "url": f"data:image/{img_format.lower()};base64,{image_data}",
                "detail": "low"
            }
        }

detail 参数控制图像分辨率:low 为低分辨率(节省 Token),high 为高分辨率(细节更丰富)。可根据业务需求调整。

7.5 统一输入组件:MultimodalTextbox

前面第五章的界面采用了文本框 + 麦克风分离的布局,存在两个问题:

  1. 不支持图片上传:界面中没有图片输入入口,无法发挥多模态模型的图像理解能力。
  2. 交互割裂:用户需要先录音等待转写,再手动点击发送,无法在一次提交中同时发送文本、图片和音频。

为此,我们将输入组件替换为 Gradio 的 MultimodalTextbox,它允许用户在同一个输入框中同时发送文本和文件:

chat_input = gr.MultimodalTextbox(
    interactive=True,
    file_types=['image', '.wav', '.mp4'],  # 支持的文件类型
    file_count="multiple",                  # 允许多文件上传
    placeholder="请输入信息或上传文件...",
    show_label=False,
    sources=["microphone", "upload"],       # 支持麦克风和文件上传
)

该组件提交的数据结构为:

{'text': '用户输入的文字', 'files': ['/path/to/file1.jpg', '/path/to/file2.wav']}

7.6 消息解析与分发逻辑

需要将 MultimodalTextbox 提交的数据解析为 HumanMessage(content=[...]) 格式,根据文件扩展名调用对应的编码函数。

def get_last_user_after_assistant(history):
    """从聊天记录中提取最后一轮 assistant 回复之后的所有 user 消息"""
    if not history or history[-1]["role"] == "assistant":
        return None

    last_assistant_idx = -1
    for i in range(len(history) - 1, -1, -1):
        if history[i]["role"] == "assistant":
            last_assistant_idx = i
            break

    return history if last_assistant_idx == -1 else history[last_assistant_idx + 1:]

def add_message(history, messages):
    """将用户输入(文件 + 文字)追加到聊天记录"""
    for f in messages['files']:
        history.append({'role': 'user', 'content': {'path': f}})
    if messages['text']:
        history.append({'role': 'user', 'content': messages['text']})
    return history, gr.MultimodalTextbox(value=None, interactive=False)

def submit_messages(history):
    """解析用户消息,构造 HumanMessage 并调用多模态链路"""
    user_messages = get_last_user_after_assistant(history)
    content = []

    if user_messages:
        for msg in user_messages:
            if isinstance(msg['content'], str):
                # 文本消息
                content.append({'type': 'text', 'text': msg['content']})
            elif isinstance(msg['content'], tuple):
                # 文件消息(Gradio 以 tuple 形式传递文件路径)
                file_path = msg['content'][0]
                if file_path.endswith('.wav'):
                    content.append(transcribe_audio(file_path))
                elif file_path.endswith(('.jpg', '.png', '.jpeg')):
                    content.append(transcribe_image(file_path))

    input_message = HumanMessage(content=content)
    resp = chain_with_history.invoke({'messages': input_message}, config)
    history.append({'role': 'assistant', 'content': resp.content})
    return history

消息构造示意:

用户发送:一段文字 + 一张图片 + 一段录音
    ↓ 解析为
HumanMessage(content=[
    {'type': 'text', 'text': '请描述这张图片中的内容'},
    {'type': 'image_url', 'image_url': {'url': 'data:image/jpeg;base64,...'}},
    {'type': 'audio_url', 'audio_url': {'url': 'data:audio/wav;base64,...'}}
])
    ↓ 传入多模态 LLM
模型同时理解文字、图像和音频内容 → 生成综合回复

get_last_user_after_assistant 的作用是:当用户连续发送多条消息(先传图片、再发文字)后才触发回复时,需要将这些消息合并为一个 HumanMessage,确保模型能完整理解用户意图。

7.7 完整界面代码

import uuid
import gradio as gr

config = {"configurable": {"session_id": str(uuid.uuid4())}}

with gr.Blocks(title='多模态聊天机器人', theme=gr.themes.Soft()) as demo:
    chatbot = gr.Chatbot(height=500, label='聊天机器人')

    chat_input = gr.MultimodalTextbox(
        interactive=True,
        file_types=['image', '.wav', '.mp4'],
        file_count="multiple",
        placeholder="请输入信息或上传文件...",
        show_label=False,
        sources=["microphone", "upload"],
    )

    chat_input.submit(
        add_message, [chatbot, chat_input], [chatbot, chat_input]
    ).then(
        submit_messages, [chatbot], [chatbot]
    ).then(
        lambda: gr.MultimodalTextbox(interactive=True),  # 回复完成后重新激活输入框
        None,
        [chat_input]
    )

if __name__ == '__main__':
    demo.launch()

事件链:

用户提交(文字 + 文件)
    ↓
add_message() → 将文件和文字分别追加到 chatbot 记录,锁定输入框
    ↓ .then()
submit_messages() → 解析消息类型 → Base64 编码 → 构造 HumanMessage → 调用多模态 LLM → 追加回复
    ↓ .then()
lambda → 重新激活输入框,允许下一轮输入

八、总结:核心组件速查表

组件核心能力本文应用场景
RunnableWithMessageHistory自动管理对话历史的读写所有章节的对话链路核心
InMemoryChatMessageHistory基于内存的历史存储开发调试阶段的快速验证
SQLChatMessageHistory基于数据库的持久化存储生产环境的对话记录持久化
RunnablePassthrough.assign透传数据并追加计算字段注入摘要逻辑到链路中
HumanMessage(content=[…])混合类型内容列表多模态输入(文本 + 图片 + 音频)
gr.Blocks构建 Web 交互界面聊天机器人的可视化前端
gr.MultimodalTextbox支持文字 + 文件的统一输入多模态聊天的用户输入组件
Base64 编码将二进制文件转为文本格式传输图片和音频的本地文件传参方式

九、完整代码


写在最后

本文从最基础的内存对话记忆出发,逐步引入持久化存储、历史摘要、Web 界面、语音识别,最终实现了一个支持文本、音频、图像的多模态聊天机器人。在这个过程中,前两篇文章介绍的提示词模板、LCEL 链式编排、RunnablePassthrough 数据透传等核心概念不再是孤立的知识点,而是被串联成了一条从后端链路到前端交互的完整链路。

如有疑问或建议,欢迎留言讨论!

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐