电商的交互方式长期被"搜索框 + 筛选器"统治:用户得会打字、会组织关键词、会在一堆参数里勾选。可真实世界里,我们想买东西时脱口而出的往往是「我想换个手机,预算 3000 以内,主要拍照用」——一句话里同时塞了品类、预算、使用场景。能不能让系统像导购一样听懂这句话,主动把缺的信息问清楚,再挑出合适的商品念给你听?
这是我最近在做的个人项目 语音购物助手(Voice Shopping) 想解决的核心问题。它基于多智能体(Multi-Agent)架构,用户全程语音交互,系统自动完成意图理解、需求澄清、商品推荐、情感应答,全链路流式处理:ASR → Agent → TTS,WebSocket 全双工通信。本文从需求和功能两个角度,聊聊这个项目是怎么把"听懂人话的导购"这件事拆解落地的。
一、需求拆解:要做一个"会听话"的购物助手,难在哪
在动手写代码前,我先把"语音购物"这个模糊概念拆成了五条具体需求。每一条都对应一个真实的工程难点。
需求 1:交互方式必须是语音,且要"边说边听"
打字搜索对双手和眼睛都有占用,而语音的最大价值是解放双手——做饭、开车、走路时都能用。这带来一个硬约束:系统不能等用户说完一整段才开始处理,否则延迟高到没法用;同时用户随时可能打断、补充。所以底层必须是全双工的:麦克风录音和扬声器播放要能同时进行。
需求 2:延迟要低,体感要像在跟人说话
人对语音延迟的容忍度远低于文字。文字回复慢一两秒无感,语音停顿超过 1 秒就会觉得"卡"。这就要求从用户开口到听到回复的整条链路(语音识别 → 理解 → 检索 → 合成)必须流式串联,每一段产出一点就立刻喂给下一段,而不是等前一段完全结束。
需求 3:得"听懂",而不是机械匹配关键词
「预算 3000 以内」要理解成价格上限,「主要拍照用」要理解成用途槽位。更要命的是用户经常信息不全——只说"换个手机",没说预算、没说用途。一个合格的导购不会瞎推,而是会反问"平时主要拍照还是打游戏?“。系统必须能判断"信息够不够推荐”,不够就主动澄清。
需求 4:要个性化,记住用户的偏好
同一个用户反复来,系统不该每次都从零问起。身高体重、常买的品牌、价格区间,这些应该沉淀成画像,影响推荐结果。同时对话内的上下文(比如刚才说过预算 500)也要记住,不能用户补充一句就忘了前面。
需求 5:能服务多个商家,数据互不串
这不是单店工具,而是 SaaS 形态——每个商家入驻后看到的是自己的商品、自己的用户、自己的订单。A 商家的用户绝不能搜到 B 商家的商品。这就要求行级数据隔离成为系统能力,而不是靠业务代码自觉。
还有一条隐性的工程需求:成本可控。每次对话都让大模型从头推理很贵,高频问题应该尽量短路掉。
二、功能全景:每条需求如何对应到具体功能
先上一张整体架构图建立全局印象,再逐项拆解:

需求清楚了,功能就是需求的工程映射。下面这张表是总览,后面逐项展开。
| 需求 | 对应功能 | 关键技术 |
|---|---|---|
| 语音交互、全双工 | WebSocket 双向音频流 + ASR/TTS | Paraformer 实时 ASR + CosyVoice TTS |
| 低延迟 | 三段流式管道 | ASR/Agent/TTS 流式串联 |
| 听懂人话、主动澄清 | 4 个专业 Agent + 状态机编排 | AgentScope Java,qwen-max/turbo |
| 个性化 | 用户画像 + 三层记忆 | 静态/动态画像合并,Redis + PG |
| 多商家隔离 | 行级 merchant_id 过滤 |
Sa-Token + MyBatis-Plus 租户插件 |
| 成本可控 | FAQ 语义短路 + 多级缓存 | pgvector 阈值命中,Redis 缓存 |
功能 1:全双工音频流——让"边说边听"成立
WebSocket 上同时跑两种帧:二进制帧传 PCM 音频(16bit / 16kHz / 单声道),文本帧传 JSON 控制消息(开始录音、停止录音、ASR 中间结果、推荐卡片)。
1 | Mic ──PCM──▶ WebSocket ──▶ ASR ──text──▶ Agent ──text──▶ TTS ──PCM──▶ WebSocket ──▶ Speaker |
上行录音和下行播放同时进行,用户说话的同时也能听到上一轮的回复尾巴——这正是全双工的体感来源。
功能 2:三段流式管道——压低端到端延迟
ASR、Agent、TTS 三段以管道方式串联,不等待前一段完全结束。ASR 产出中间文本时即可触发 Agent 推理,Agent 产出文本片段时即可触发 TTS 合成。这是把"语音回复延迟"压到可接受范围的核心手段。
功能 3:多智能体编排——把"听懂 + 反问 + 推荐 + 应答"拆给专职 Agent
这是整个系统最核心的设计。一个"全能大模型"看似能搞定一切,但在工程上,把意图理解、需求澄清、商品推荐、情感应答混在一个 Prompt 里,既难调试、又贵、效果还不稳定。我把它们拆成 4 个专职 Worker Agent,再用一个手写的 Orchestrator 状态机显式编排。
1 | ┌─────────────────────────────────────────────────┐ |
四个 Agent 各司其职:
| Agent | 职责 | 输入 | 输出 |
|---|---|---|---|
| IntentAgent | 意图理解 | 用户话语 + 近期历史 | 意图、槽位、置信度 |
| ClarifyAgent | 需求澄清(规则优先,LLM 兜底) | 当前槽位 | 反问 or 就绪 |
| RecAgent | 商品推荐 | 槽位 + 用户画像 | 推荐列表 |
| EmotionAgent | 情感应答 | 推荐结果 + 用户情绪 | 口语话术 + 展示卡片 |
为什么用手写状态机而不用现成的 Agent pipeline?因为购物对话的分支是确定性的业务规则(意图决定走哪条链路),不是模型自由发挥。显式编排让链路可读、可测、可降级。意图路由表如下:
| 意图 | 走的链路 |
|---|---|
| 商品推荐 | ClarifyAgent → RecAgent → EmotionAgent |
| 商品对比 | RecAgent → EmotionAgent |
| 需澄清 | ClarifyAgent(循环直到信息齐备) |
| 确认下单 | 直接走订单流程 |
| 闲聊 | EmotionAgent 直接应答 |
| 超出范围 | EmotionAgent 礼貌拒绝 |
功能 4:向量检索——让"语义"取代"关键词"
商品和 FAQ 都做语义搜索,底层是 PostgreSQL + pgvector。关键设计是在 SQL 层一次性完成向量检索 + 属性过滤,而不是先 ANN 再用代码过滤:
1 | -- HNSW 向量检索 + JSONB 属性过滤,一次查询搞定 |
vector(1024) 列存 text-embedding-v3 的语义向量,HNSW 索引保证检索速度,JSONB 列存灵活属性(品类、规格)用于硬过滤。这样"手机"能召回"智能手机"“5G 手机”,而不会被关键词字面匹配卡住。
功能 5:FAQ 语义短路——成本可控的关键一招
高频问题(“怎么退货”“支持货到付款吗”)如果每次都走大模型对话,又慢又贵。系统先做向量检索,相似度 ≥ 0.75 直接命中 faq_entry 表的预写答案,根本不进 LLM 对话链路;低于阈值才走 LLM 兜底。这一招把大量重复提问挡在了 token 消耗之外。
功能 6:用户画像 + 三层记忆——个性化与上下文连续
画像分静态(身高、体重等基本属性)和动态(浏览/购买行为沉淀出的品类、品牌偏好)两类,合并成不可变的 UserProfileSnapshot 供所有 Agent 读取,Redis 缓存 24 小时。用户每次浏览、购买都会触发行为回流事件,实时更新动态偏好并刷新缓存。
记忆则是三层架构,对应不同时间尺度:
- 短期记忆(Redis List,30min TTL)——最近 N 轮对话,供 IntentAgent 读 3 轮上下文、EmotionAgent 读 2 轮情绪趋势;
- 会话状态(PG + Redis 双写)——Orchestrator 状态机本身,读优先 Redis,写同时落 PG;
- 长期记忆(PG user_profile)——跨会话的画像和历史行为,会话中提及的品类/品牌会回流写进动态画像。
功能 7:多商家行级隔离——SaaS 的安全底线
每张业务表都带 merchant_id,靠 MyBatis-Plus 租户插件自动注入 WHERE 条件,业务代码无感知:
1 | // 自动注入: WHERE merchant_id = #{currentMerchantId} |
鉴权用 Sa-Token:REST 走手机号登录,WebSocket 走 Sub-Protocol token,连进来的 Channel 解析出 SessionScope(即"哪个用户、在哪个商家"),后续所有检索都强制带这个 scope 过滤。
对订单这类用户/商家私有数据,还有一条更严的"主体强制过滤"原则——Repository 查询方法必须显式带 userId/merchantId 维度(如 findByIdAndUserId),让"忘传身份"在编译期就被拦下,而不是靠"先查出来再判 ownership"的事后补救。后者一旦遗漏就直接越权。
功能 8:订单闭环——从推荐到下单的最后一公里
推荐完只是开始,真正闭环是下单。系统设计了 preview → confirm → cancel 三态流转:推荐结果先解析出具体商品引用(OrderReferenceResolver),用 Redis 暂存预览态(PendingOrderStore),用户确认后才真正扣库存。库存扣减走 PostgreSQL 原子操作防超卖,下单成功后在事务内发布事件,画像和长期记忆通过 @TransactionalEventListener(AFTER_COMMIT) + @Async 异步回流——保证"先落库,再更新画像"的顺序,即使画像更新失败也不影响订单。
三、一个完整交互走查
光说架构不直观,拿开头那个例子走一遍。用户说「我想换个手机,预算 3000 以内」:
1 | 1. IntentAgent |
把这段多轮交互画成时序图,箭头方向和数据流向一目了然:

第 6 步的 speechText 流式喂给 TTS 合成音频,displayBlocks 通过 WebSocket 文本帧推到前端渲染成推荐卡片。用户耳朵听到话术的同时,眼睛看到商品卡——听觉和视觉信息同步到达。
四、开发过程:从骨架到闭环的演进
这个项目按六个阶段推进,git log 完整记录了从骨架到闭环的演进。整体节奏遵循一条清晰的顺序:先打通数据,再叠加智能,最后抠性能与成本。每一步都对应一个可独立验证的能力增量。
阶段一:搭骨架、定契约
- 初始化 Maven 多模块骨架,定下
web → business → ai → infrastructure → common的单向依赖; - 同步写系统架构文档和架构图——先画图后写代码,避免后期返工;
- 落地向量检索与数据契约规范,把表结构、DTO 提前定死。
这一阶段最关键的决策是「契约先行」:DTO、表结构、模块边界在写第一行业务代码前就锁定,后面所有 Agent 都按契约对接,省掉了大量联调摩擦。
阶段二:打通数据与语音两条链路
- 商品向量检索 + FAQ 问答全链路(pgvector + DashScope embedding);
- 用户画像服务、会话管理、短期记忆;
- 语音通道 ASR/TTS 流式链路 + WebSocket 全双工通信;
- Agent 工厂与 Prompt 加载器基础架构。
这一阶段把「数据能查到」和「语音能进出」两件最底层的事各自跑通,且互不依赖——为后面 Agent 编排留出了干净的接入点。
阶段三:四个 Agent 逐个落地
- ClarifyAgent:规则优先 + LLM 兜底的混合澄清链路;
- RecAgent:商品推荐管线;
SentimentAgent → EmotionAgent改名;- EmotionAgent 情感应答;
- 多视角点评团 + 推荐并行合流 + 事件总线。
这一阶段是「智能」密度最高的环节。改名那次提交值得记一笔:一个准确的名字能纠正对职责的误解——它不只是「分析情感」,而是「带着情感去应答」。
阶段四:编排层串起一切
- OrchestratorService 编排层 + ComplianceChecker + WebSocket 接入;
- 会话级短期记忆与跨会话长期记忆优化。
四个 Agent 各自能跑后,还需要一个「指挥」。Orchestrator 按意图路由把它们串成完整链路,至此「用户说一句话到听到回复」的主干道贯通。
阶段五:SaaS 化与商业闭环
- 多商家数据隔离与权限模型(Sa-Token + 行级
merchant_id); - 订单查询主体强制过滤(编译期拦截越权);
- 下单确认闭环:PG 原子扣库存防超卖 +
AFTER_COMMIT事件回流。
这一阶段把「能聊天」升级成「能做生意」。两个安全设计是重点:行级隔离让多商家互不串数据;主体强制过滤把越权风险在编译期拦下,而不是靠事后 if 判 ownership。
阶段六:性能与成本
- 延迟优化与 H5 流式改造(压低端到端延迟);
- LLM/ASR/TTS 成本控制 + logfmt 结构化埋点。
最后阶段回归工程现实:延迟决定能不能用,成本决定能不能上线。FAQ 短路、多级缓存、logfmt 日志都是这一阶段补上的「生产级」打磨。
六个阶段下来,每个阶段的提交都对应一个可独立验证的能力增量——这其实解释了「功能全景」里那些功能为何能稳定存在:它们不是一蹴而就设计出来的,而是一层一层垒起来的。
五、模块划分与技术栈
工程是 Maven 多模块,依赖严格单向:
1 | voice-shopping/ |
依赖方向:web → business → ai → infrastructure → common,禁止反向和循环。
技术栈一览:
| 层级 | 技术 | 说明 |
|---|---|---|
| 语言/框架 | Java 21 + Spring Boot 4.0.5 | 虚拟线程 + REST/WebSocket |
| Agent | AgentScope Java 1.0.11 | ReActAgent + 手写 Orchestrator |
| 大模型 | qwen-max / qwen-turbo | 主对话 + 推荐排序 |
| 向量化 | text-embedding-v3 (DashScope) | 1024 维语义向量 |
| 语音 | Paraformer ASR + CosyVoice TTS | 阿里云 NLS,流式 |
| 数据库 | PostgreSQL + pgvector | 关系数据 + HNSW 向量检索 |
| 缓存 | Redis 7.x | 对话状态、短期记忆、画像缓存 |
| 权限 | Sa-Token 1.45.0 | 多商家认证 + 行级隔离 |
六、小结
回过头看,这个项目的核心思路其实就两条:
- 把模糊的"智能"拆成确定性的工程。"听懂人话"不是靠一个大模型硬扛,而是拆成意图、澄清、推荐、应答四个专职 Agent,用状态机显式编排——可读、可测、可降级。
- 让每个需求落到一个可验证的功能点上。全双工对应 WebSocket 双向音频流,低延迟对应三段流式管道,个性化对应画像 + 三层记忆,多商家对应行级隔离 + 主体强制过滤,成本可控对应 FAQ 短路 + 多级缓存。
语音购物只是个壳,真正有意思的是这套"多智能体 + 向量检索 + 流式管道"的组合范式——它同样适用于语音客服、语音问诊、语音政务等任何"自然语言驱动 + 检索增强 + 实时交互"的场景。后续打算再聊聊里面的几个深水区:全双工音频流的握手细节、ClarifyAgent 的规则优先策略、以及订单事务与异步事件回流的边界处理。