[控场AI]
· 9 分钟阅读· 4,730 字

用 LangChain 构建多模态 AI 客服:图文语音一次搞定

用 LangChain 构建多模态 AI 客服:图文语音一次搞定

用 LangChain 的多模态消息能力,让客服 Agent 主动索取截图和语音,把说不清的问题变成看得见的证据。

文章以一个记账 App 导出报错场景切入,指出纯文本客服在界面类问题上的表达天花板,进而演示如何用 LangChain 构建多模态客服 Agent:通过 `init_chat_model` 加载支持图像与音频的 Gemini 模型,用 `create_agent` 加系统提示词约束行为边界,再借 `HumanMessage` 的内容块列表把 Base64 编码的截图与语音与文本组合发送。共享的 `thread_id` 配合 checkpointer 保证补充的媒体挂接到同一会话上下文。文章还解释了多模态消息结构、Base64 的传输作用、对话记忆机制与模型媒体支持限制,并提醒隐私边界与成本取舍——界面报错、设备异常类场景收益明显,而账号、订单类咨询纯文本可能更划算。

传统的 AI 客服大多停留在纯文本对话:用户描述问题,机器人根据文字猜测原因。但当问题发生在具体界面上时,文字往往说不清楚——用户只能反复描述"就是那个按钮点了没反应"。LangChain 提供的多模态消息能力,让客服 Agent 可以主动索取截图和语音,把"说不清的问题"变成"看得见的证据"。

用 LangChain 构建多模态客服 Agent

问题场景:文字描述的天花板

设想一个记账 App 的导出功能报错场景。用户第一句话通常是:"我的记账 App 导出月度报表时报错了,能帮我看看吗?"

这句话包含了足够多的问题信号,但缺少定位问题所需的关键信息:错误发生在哪个界面?报错文案是什么?用户点击导出时处于什么操作状态?纯文本 Agent 只能继续追问,用户则要用文字还原一个视觉场景,效率和准确度都很低。

多模态 Agent 的思路不同。它同样先接收文字描述,但会在判断信息不足后,主动要求用户上传一张错误截图,并附上一段简短的语音说明当时在做什么。用户不需要重新开始对话,补充的图片和音频会挂接到同一个会话上下文中。

需要说明的是,这个 Agent 的角色是"提供排查建议",而不是声称自己已经修好了用户的设备。这条边界在系统提示词中被明确写死,避免模型产生误导性的修复声明。

构建多模态 Agent 的关键步骤

初始化支持图像与音频的模型

代码的第一部分是初始化一个能够同时接收图像和音频输入的模型。示例中使用的是 Gemini 系列模型,并通过 LangChain 的集成方式加载:

pip install -U "langchain[google-genai]"
export GOOGLE_API_KEY="your-google-api-key"

在 Python 侧,通过 init_chat_model 指定模型后,再用 create_agent 创建 Agent。系统提示词中明确定义了行为准则:当用户描述错误但细节不足时,要求他们上传截图并发送简短语音;使用提供的截图和音频理解问题;需要时请求澄清;不得声称已经修复任何东西。

Agent 创建时传入了一个 InMemorySaver 作为 checkpointer,用于在多次调用之间保留对话状态。

LangChain 的 init_chat_model 与 create_agent

init_chat_model 是 LangChain 提供的模型初始化统一入口,它允许开发者用字符串标识(如 gemini-2.5-flash)指定模型,并自动路由到对应的底层集成包,而无需手动 import 具体的 ChatModel 类。这种方式的好处是切换提供商时只需改一个字符串,业务代码几乎不动。

create_agent 则是构建 ReAct 风格 Agent 的工厂函数:它接收模型、工具列表和系统提示词,内部组装成「模型推理 → 工具调用 → 观察结果 → 继续推理」的循环。本示例没有挂载外部工具,Agent 的行为完全由系统提示词约束,因此它的「多模态」体现在输入侧——能否接收并理解图像与音频块——而非输出侧调用工具的能力。

用 thread_id 维持上下文

整个支持工单围绕一个固定的 thread_id 展开:

thread_config = {"configurable": {"thread_id": "support-case-1042"}}

这个 ID 在每一轮消息调用中重复使用,告诉 Agent 后续的截图和录音与最初的问题属于同一个支持会话。不同的客户或工单应当使用不同的 thread ID,避免上下文串台。

第一轮调用只发送纯文本的用户消息。Agent 返回响应后,通常会要求补充截图和语音材料。

把图片和音频编码进消息

用户准备好 error.png 截图和 issue_details.wav 语音文件后,代码用 Base64 读取这两个本地文件:

image_base64 = base64.b64encode(Path("error.png").read_bytes()).decode("utf-8")
audio_base64 = base64.b64encode(Path("issue_details.wav").read_bytes()).decode("utf-8")

随后构造一条包含三个内容块的 HumanMessage:一个文本块说明"这是你要的截图和语音,请用来帮助排查导出错误",一个图像块携带 Base64 数据和 image/png 的 MIME 类型,一个音频块携带 Base64 数据和 audio/wav 的 MIME 类型。

MIME 类型必须与实际文件格式一致,否则模型侧可能无法正确解析。

多模态消息背后的技术机制

多模态消息的结构

多模态消息指的是除文本之外还包含图像、音频等内容的消息。在 LangChain 中,HumanMessage 的 content 可以是一个内容块列表,把文本与媒体组合在一起。每个块通过 type 字段标识内容类型,base64 字段承载编码后的文件数据,mime_type 标识格式。

模型接收这条消息后,可以直接利用其中的媒体内容进行响应,而不需要额外的转换步骤。这正是多模态客服能够"看图听声"的基础。

Base64 编码的作用

base64.b64encode(Path(...).read_bytes()).decode("utf-8") 这行代码完成了三件事:读取本地文件的原始字节、将字节编码为 Base64 字符串、把结果转换为可放入内容块的 Python 字符串。

Base64 的价值在于让二进制文件能够以文本形式嵌入 JSON 风格的消息结构中。MIME 类型则承担"格式声明"的职责——示例中用 image/png 对应截图,用 audio/wav 对应语音。

MIME 类型与内容块 schema

MIME(Multipurpose Internet Mail Extensions)类型是描述数据格式的标准标识,形如 类型/子类型,如 image/png、audio/wav。在多模态消息中,模型提供商需要它来选择合适的解码器:同一段字节流若标注为 image/jpeg,解码方式与 image/png 不同,Provider 也可能据此拒绝不支持的格式。

内容块的 schema 由各 Provider 的 API 定义,LangChain 做了统一封装,但字段名和取值范围并不在所有 Provider 间完全一致。例如部分 Provider 接受 image_url 引用远程 URL,而本示例使用内联 base64;音频块在某些 Provider 上要求指定采样率或编码容器。因此切换模型时,最容易出问题的往往不是认证,而是这些媒体块字段的兼容性,需对照 Provider 最新文档核对。

对话记忆如何工作

共享的 thread_id 是上下文连贯的关键。它让 Agent 知道截图和录音属于同一个支持会话,而不是一个全新的、无关联的问题。checkpointer 在每次调用之间保存对话状态,使 Agent 能够把更早的消息作为上下文使用。

示例中的 InMemorySaver 适合小型演示,但它的检查点只存在于当前运行的进程中,进程停止后就会消失。生产环境中需要换成持久化的 checkpointer,并配合合适的数据保留策略。

checkpointer 与 thread_id 的关系

checkpointer 是 LangChain 中负责持久化「对话状态快照」的组件。每当 Agent 完成一轮处理,checkpointer 会把当时的完整消息列表、中间步骤等状态按 thread_id 存入后端存储。下一次用相同 thread_id 调用时,Agent 会先读取该线程的历史状态,再在其基础上追加新消息。

这也解释了为什么不同工单必须使用不同 thread_id:thread_id 实质上是状态的命名空间,同一个 ID 下的所有消息会被拼接成一段连续上下文。若两个用户的会话共用了同一个 ID,彼此的截图、语音和文字就会互相「串台」,导致模型基于错误背景作答。InMemorySaver 把状态存在进程内存的字典里,天然不支持跨进程共享,也无法在多副本部署中保持一致,因此只适合本地调试。

模型与媒体支持的限制

具体的格式和限制取决于模型提供商。示例选用的 Gemini 模型支持图像和音频输入,但并非所有模型都接受所有媒体类型或内容块格式。提供商当前支持的格式和大小限制需要查阅其最新文档。

一个容易被误解的点是:把文件编码成 Base64 并不会让纯文本模型突然具备理解能力。媒体支持是模型层面的能力,编码方式只是传输手段。

在音频处理上,不同提供商的行为也有差异——音频可能被转写、被直接理解,或者需要提供商特定的格式。如果标准音频块不被支持,应当使用提供商文档规定的格式,或者先把录音转成文字再发送。

隐私边界不能忽略

截图和录音中可能包含个人或机密信息。实践中有几条底线:只发送你有权处理的文件,避免不必要地记录敏感媒体内容,并且意识到 checkpointer 可能保留消息内容。

对于客服场景,这意味着在设计阶段就要考虑媒体文件的生命周期——哪些需要落盘、保留多久、谁能访问。这些问题不会因为用了多模态而自动解决,反而因为引入了图片和音频变得更加复杂。

媒体数据在链路中的生命周期

多模态客服引入了一条通常被忽视的数据链路:用户上传的截图和录音不仅会进入模型推理,还可能被 checkpointer 连同对话状态一起落盘,甚至出现在日志、trace 或监控面板中。Base64 文本本身不提供任何加密或脱敏,它只是编码方式,任何能读到消息体的角色都能还原出原始图片和音频。

实践上需要在几个环节做控制:上传前做客户端侧的敏感区域打码(如身份证号、订单金额);服务端为媒体设置独立的存储桶和短生命周期,避免与对话文本长期混存;对 checkpointer 配置 TTL 或定期清理策略;在 trace 系统中对 base64 字段做截断或脱敏,防止监控链路成为新的泄露面。这些措施应作为上线前的默认配置,而非事后补救。

落地时的取舍

这套方案的核心价值在于把"文字说不清的问题"转化为"可以直接查看的证据",降低来回追问的成本。但它的代价同样明显:媒体文件带来更高的传输和存储开销,模型侧的 token 消耗也高于纯文本;同时,隐私合规的要求更严格。

对于需要处理界面报错、设备异常、操作流程类问题的客服场景,多模态 Agent 的收益通常能覆盖这些成本。而对于纯账号、订单、政策类的咨询,纯文本方案可能更轻、更快。选择哪种形态,取决于你的用户到底"卡"在哪里。

#LangChain 多模态#AI 客服 Agent#多模态消息#LangChain HumanMessage#对话记忆 thread_id#Base64 编码#Gemini 多模态输入#AI 客服开发
分享:

相关推荐