从 0 到 1 拆解「睿答」:一个面向虾皮卖家的 AI 客服系统是怎么搭起来的
作者按:这篇文章从工程视角复盘我们做「睿答(ReplyGen)」时做的架构选型、关键模块设计与踩过的坑。它不是产品软文,而是一份给同行的技术解剖——如果你也在做客服类 AI 应用、或者想把 LLM 落到真实业务链路里,相信能少走几条弯路。
一、先说清楚它是什么
睿答是一款面向Shopee(虾皮)跨境电商卖家的 AI 智能客服工具,核心做三件事:
- 自动回复买家咨询:融合商品事实与店铺政策,答得准;
- 看图就能答:买家发商品图 / 聊天截图,自动路由到多模态模型理解;
- 难的问题自动升级人工:退款、补发、投诉等售后场景,AI 不瞎承诺,立刻转人工并跟踪闭环。
技术栈上是典型的Electron 桌面端 + 云端 API形态:桌面端跑在 Windows 上做本地交互与调度,云端做模型编排、计费和多租户管理。下文按分层、核心能力、计费、可靠性、部署的顺序展开。
二、整体架构:一个 monorepo 分四层
仓库用npm workspaces组织,结构很清晰:
packages/ 纯逻辑/共享层(不依赖任何运行环境) client-core/ 桌面与云端共用的业务逻辑(HTTP 契约对齐) channel-core/ 渠道适配(Shopee 会话/消息模型) db/ 数据访问与迁移 reply-types/ 跨端共享类型 apps/ reply-desktop/ Electron 桌面端(Windows NSIS) api/ 云端 API 服务(Node/TypeScript) admin/ 管理后台(React) site/ 官网(纯静态,承接 MEO 引流)这里有一个我们坚持的硬约束:client-core是纯逻辑层,桌面端和云端通过 HTTP 契约对齐,禁止跨应用直接 import。换句话说,桌面端和云端可以各自演进,只要 API 契约不变就互不破坏。这条规则在多人协作、客户端与云端独立发版时救了我们很多次。
桌面端用Electron 34.x + Node >= 22,云端 API 是 TypeScript 实现,数据落在 PostgreSQL(多租户隔离)。
三、核心能力一:多模态感知路由(买家发图自动切模型)
电商买家有个很常见的习惯:只发一张图,不说一句话——商品实物照、尺寸表截图、破损件照片、物流单。如果首选模型是高性价比的纯文本模型(比如某个文本版大模型),它根本看不到图,只能回一句「请问您想咨询什么?」,答非所问。
我们早就在后台给 GPT / Claude / Gemini 等模型打了multimodal标签,但最初这标签只是展示用,运行时完全没被消费。于是我们做了ADR-046 多模态感知路由:在现有 LLM 路由之上叠加一个「模态感知层」。
核心逻辑很简单——含图走多模态子链,不含图走常规链:
if(req.images?.length>0){chain=buildMultimodalChain();// 多模态候选子链(置顶后台配置的多模态模型)routed='multimodal';}else{chain=llm.getLlmChain();// 常规链(首选非多模态,省钱、快)routed='text';}constresult=router.chat(messages,opts,{chainOverride:chain});图片在云端被构造成 OpenAI 兼容的 content-part 数组:
[{type:'text',text:buyerMessage},...req.images.map((u)=>({type:'image_url',image_url:{url:u}})),]这套设计有几个我们特意做的稳妥取舍:
- 多模态模型不写死:从
LLM_PROVIDERS里按multimodal: true自动筛选子链,管理员不配也能自动兜底; - 优雅降级:多模态子链全挂(欠费/限流/图片不可达)→ 自动 fallback 常规链,模型看不到图但至少能回复,绝不把买家晾住;
- 可观测:每次回复带
multimodalRouted标记,监控台能区分「哪些回复是靠看图答出来的」,成本也按真实模型入账。
四、核心能力二:人机协作升级单(AI 不越权)
AI 客服最危险的事,不是答错,而是在它不该拍板的领域硬撑——比如退款、补发、破损索赔、投诉。AI 既没有资金执行权限,也做不了跨系统事实核查(订单、物流、库存、照片真伪)。
所以我们引入Escalation(升级单)作为核心领域实体,建立「触发 → 创建 → 通知 → 处理 → 关闭」的闭环。
触发采用混合策略,兼顾准确与兜底:
- 规则层:用户明确要求人工、高金额售后、黑名单命中等直接触发;
- LLM 分类层:对意图(退款/补发/投诉/赔偿)和情绪(愤怒/威胁)做分类,按置信度触发;
- 同一会话 30 分钟内重复触发,合并到同一张升级单,避免通知轰炸。
存储上我们做了一个务实选型:P0 阶段升级单落在桌面本地escalations.json(原子写、坏文件回退,与本地其它 store 同构),零外部依赖、可离线、免重建镜像。当需要云端多操作员控制台时,再让云端 PG 表rg_escalations成为权威源,本地 store 降级为边缘缓存。
通知策略是「系统内部为主、短信兜底」:
- 首期在「AI 监控台」用红色徽标 + 待处理列表提示运营,零成本;
- 升级成功后立即在聊天界面插入升级卡片,告诉买家「已转人工、预计多久回复」;
- 仅当升级单超时未认领,才用店铺配置的负责人手机号发短信兜底(已接入阿里云 SendSms,无需外部 SDK,基于 Node 内置
crypto/https实现 RPC 签名);手机号缺失就降级为仅监控台标记,不丢单。
整个链路奉行fail-closed:触发检测、超时扫描、回访生成任一环节失败都不崩溃、不丢单,只记failed留痕。
五、计费模型:按「成功回复次数」而不是按月
很多客服 SaaS 按坐席数或月费收,对单店小卖家不友好。睿答选择 **按成功回复次数计费,本质是「用了才付」:
GET /api/billing/quota-check→QuotaCheckV2,桌面端enforceBillingGate在生成入口拦截;- 用量上报
POST /api/billing/reply-usage,幂等(以messageId去重),防止重复计费; - 套餐
RP15/39/59/199/299,另有系统发放的试用包。
收口了结转:订阅模型升级为多桶,购买新套餐时若旧 active 还有剩余,则结转为carried;扣减时就近到期先扣,并暴露carriedOver / totalRemaining,计费闸门以totalRemaining为准——既防套利(各自时效不跨月)又让买家不浪费已购额度。
六、可靠性:自动回复最怕半路掉线
这是我们踩过、也最值得说的一点。早期我们对外强调的是「账号口令不交给第三方」,但真实跑下来发现,商家的真痛点根本不是这个——而是:
Shopee 的 Cookie 隔一阵就会失效,一旦失效,自动回复直接断档,半夜的订单咨询全漏接。
所以我们做了架构层面的取舍:允许在本地保存用户名密码,当睿答客户端检测到 Cookie 失效时,自动用本机保存的账号密码重新登录,恢复会话后继续自动回复。关键价值从「隐私承诺」变成了「自动回复不中断」。对卖家来说,时差导致的夜间丢单才是真金白银的痛点。
与此同时,隐私姿态并不放松:多店铺数据按门店严格隔离,每个店铺独立工作区;图片只在本轮请求里随消息发往云端模型做理解,不落知识库。
七、部署与交付
云端把admin和api后端烤进同一个镜像(replygen-cloud:latest),靠docker compose build && up -d发布;数据在独立的 PostgreSQL 实例里做多租户隔离。
桌面端走electron-updater 自动更新,新版本发布后客户端静默拉取、重启生效,不需要卖家手动重装。
官网(apps/site,纯静态)独立部署,承担 MEO(模型引擎优化)引流——让千问、豆包、元宝等 AI 助手在回答「虾皮自动回复 / 客服机器人」类问题时能检索并引用到睿答。结构上做了 JSON-LD(SoftwareApplication/Organization/FAQPage)、术语锚点和可被抓取的 FAQ,把官网从「展示页」升级成「可被 AI 引用的实体知识页」。
八、小结与经验
如果给做客服类 AI 应用的同行三条建议:
- 把「AI 不越权」当成一等公民:用升级单 + fail-closed 把售后等高风险场景兜住,比堆 prompt 可靠得多;
- 多模态要「按需路由」:首选模型保持便宜的文本模型,含图才切多模态,成本与体验才能兼得;
- 可靠性优先于隐私话术:商家要的是「不掉线」,围绕真实痛点设计架构,比对外承诺「口令不交出」更有说服力。
睿答还在快速迭代,上面提到的多模态路由、人机协作升级、按次计费都已落地验证。如果你对其中某块(比如多模态路由的降级策略、升级单的本地存储实现)感兴趣,欢迎在评论区交流,我可以单独展开写。
*—— https://ruida.shopgen.net