闲鱼自动发货系统:基于大模型意图识别的智能客服与库存管理实践
2026/9/16 20:56:35 网站建设 项目流程

凌晨一点半,手机屏幕亮起来,又是那句“在吗,这个还有货吗”。我爬起来,复制粘贴卡密、回复“亲,自动发货,请确认收货”,然后躺下。这样的动作,过去一年我重复了超过四千次。我不是大卖家,只是一个在闲鱼上卖软件授权和资料包的兼职卖家,但正是这四千次重复让我下决心写了这个项目:XianYuAutoDeliveryX,一个基于闲鱼消息监听、大模型意图识别和库存管理实现的智能自动发货系统。

这个项目解决的核心问题很直接:虚拟商品的交易咨询高度重复,发货动作机械枯燥,人工处理不仅慢,而且夜间完全无法覆盖。而传统的关键词自动回复方案又太笨,买家一句“老板在吗”稍微换个说法就匹配不上。大模型的出现改变了这个局面——意图识别终于可以做到“像人一样理解消息”,于是我把闲鱼消息流、大模型推理和执行动作串成了一条自动化流水线。这篇文章我会完整拆解这个系统的架构设计、关键代码、模型选型思路和上线后的真实数据,适合正在做电商自动化、虚拟商品交易,或者单纯对“大模型 + 业务系统”落地感兴趣的开发者参考。

1. 为什么闲鱼虚拟商品卖家需要一套自动发货系统

1.1 虚拟商品交易的重复劳动成本

先算一笔时间账。我观察了自己一个月的数据:日均订单大概 40 单,每单从买家咨询到确认收货,平均需要 8 条消息交互。其中有 6 条是高度雷同的——“在吗”、“有货吗”、“怎么发给我”、“不能用怎么办”。真正需要人工介入的只有“要换货”、“发票”这类异常情况,占比不到 15%。

这就意味着,85% 的沟通和发货动作完全是在消耗时间,没有创造任何增量价值。对一个兼职卖家来说,这些时间本可以用来选品、优化描述、处理售后。而对全职卖家来说,请一个客服的成本远超自动化改造的投入。虚拟商品(卡密、授权码、网盘链接)和实物商品最大的区别在于:交付动作可以被完全数字化。整个交付链路由字符串组成,天然适合用代码接管。

但现实是,大部分中小卖家仍然在手动发货。原因很简单:平台没有开放的自动发货 API,市面上的辅助工具质量参差不齐,而自己开发又面临消息获取、意图理解、防重复发货三道坎。这三道坎,恰好分别对应了数据接入、大模型推理、工程健壮性三个技术领域。

1.2 传统自动回复方案为什么行不通

市面上已有的自动发货方案主要有两类。第一类是“定时自动回复”,预先设置好一组关键词和对应的回复内容,比如买家发送“卡密”就自动回复一串字符。这种方案的致命缺陷是泛化能力为零:买家说“密”它不触发,买家说“查一下卡号”它也不触发,一个“在吗”就直接把机器人打回原形。

第二类是“网页版闲鱼挂机 + 浏览器插件”,通过 DOM 操作模拟点击和输入。这类方案没有意图理解能力,而且依赖页面结构,闲鱼前端一改版就全部失效,维护成本极高。

这两类方案的共同问题是:把“发货”理解成了“查表”。但真实交易场景里,买家的表达是开放式的、具备隐含意图的。大模型的思维链推理能力正好可以在这个环节发挥作用——它不依赖固定模板,能够根据上下文判断“这个人到底是想买、想退、还是在问使用方法”。这就是大模型在自动发货这个场景中真正的不可替代性。

1.3 大模型在这里的价值不是“聊天”

很多人听到大模型就想做聊天机器人,但在 XianYuAutoDeliveryX 里,大模型承担的是一个更具确定性的任务——意图分类和信息抽取。系统把“识别买家意图”转化为一个分类任务:BUY、ASK_USAGE、ASK_STOCK、AFTER_SALE、OTHER。同时从消息中抽取关键实体:商品名、数量、邮箱等。

这种任务设计的优势有两个。第一,输出是结构化的 JSON,后续逻辑可以直接消费,不需要跑额外的 NER 模型。第二,分类任务的幻觉风险远低于开放对话——模型只需要从有限的标签中选择一个,犯错空间被极大压缩。实测下来,用 Qwen 7B 量化的本地模型做这个分类,5 分钟内的消息准确率能到 95% 以上,完全够用。

2. 系统整体架构:从消息触发到卡密送达的完整链路

2.1 系统分层与模块职责

整个系统我分成了三层,每一层只做自己的事,通过消息队列解耦。

  • 接入层:负责从闲鱼端获取消息数据。这里有一个现实约束——闲鱼没有公开的开放平台 API,所以常规做法是使用模拟登录后的内部接口,或者基于安卓自动化协议(Appium + uiautomator2)做界面层的数据采集。为了不引入平台违规风险,我的实现采用了更克制的策略:只用只读方式获取消息列表和聊天内容,不执行任何点赞、评论、刷单等写操作。这一层输出的是一条包含 session_id、sender_id、text、timestamp 的标准化消息对象,交给下游处理。
  • 理解层:这是整个系统的“大脑”。拿到消息后先做前置过滤,再交给大模型做意图分类,最后把分类结果和置信度一并输出。这一层还负责处理上下文——同一个会话窗口内的多条消息会被拼接为一段对话历史,让模型能理解“这个手机号”指代的是前一条消息里出现的号码。
  • 执行层:根据理解层的输出触发具体动作。买家表达购买意图时,执行层去库存表里锁库存、生成发货消息(卡密或链接)、通知发货完成。关键设计是“动作审核”机制——只有指令是“发送卡密”且库存校验通过时才真正执行,避免由于模型误判导致错误发货。

2.2 状态机:把交易流程变成可推理的状态流转

自动发货最怕的就是状态混乱:同一笔订单被发货两次,或者买家还没付款就收到了卡密。为了解决这个问题,我把每一笔会话的状态建模为有限状态机。

INIT -> AWAIT_PAYMENT -> PAID -> DELIVERED -> COMPLETED ↘ FAILED ↘ REFUNDED

状态之间的跳转由事件驱动。比如收到消息“拍了,怎么付款”,这只是一个咨询事件,不改变状态;只有系统确认订单支付成功的回调到达后,状态才会从 AWAIT_PAYMENT 切换到 PAID。实际上,闲鱼并没有给我们支付回调的能力,所以我用的方式是轮询订单状态 + 消息触发兜底:如果检测到买家发来“已付款,发一下”,系统会先调订单查询接口确认真实支付状态,确认后才执行发货。这个“先验真、再发货”的策略,是规避资损的关键。

字段说明
session_id会话唯一标识,用于关联消息上下文
order_status当前订单状态(INIT / AWAIT_PAYMENT / PAID / DELIVERED / FAILED)
goods_id商品 ID,决定从哪个库存池扣减
deliver_token发货幂等令牌,一个订单仅生成一次
last_intent最近一次模型识别出的用户意图
confidence最近一次意图的置信度

2.3 数据存储与消息队列的选型

数据存储我用了 SQLite + Redis 的组合。SQLite 保存会话记录、订单记录和发货日志,单文件部署,备份方便;Redis 用来做消息幂等去重和库存扣减校验。为什么不用 MySQL?因为个人项目的并发量远没到需要独立数据库服务的程度,SQLite 在 WAL 模式下读写性能足够,而且部署复杂度低一个量级。

消息队列选型上,我没有引入 Kafka 或 RabbitMQ,而是直接用 Redis 的 Stream 结构。每条从接入层进入的消息会先写入 Stream,由消费者拉取后再送入理解层。选用 Stream 的原因有两个:一是天然支持消费者组,多条消息可以并行处理;二是消息有持久化能力,即使消费者进程崩溃重启,也不会丢消息。实际运行中,消息处理吞吐量保持在每秒 5 条以内,压力很小。

3. 核心技术拆解:闲鱼API的边界、意图识别设计与本地大模型部署

3.1 闲鱼API获取数据的现实方案和合规边界

先说一个必须明确的事情:闲鱼官方没有开放面向个人开发者的自动发货 API。市面上的所谓“闲鱼API”通常是两种来源:一种是通过协议逆向得到的内部接口(非官方、有封号风险),另一种是企业版闲鱼开放平台能力(门槛高、审核严,通常只面向品牌方)。

我在项目里采用的方案偏向保守:基于安卓虚拟化环境的界面自动化。具体逻辑是——通过 uiautomator2 连接一台运行闲鱼 App 的安卓设备,定期抓取指定会话的聊天列表页,把最新消息的控件文本提取出来。这个过程只读取界面上已经渲染出来的消息内容,不触碰协议层,也不发任何额外请求,从风控角度来说比逆向内部 API 安全得多。

如果基于通知栏消息来做,也是一种轻量级替代:安卓端使用 NotificationListenerService 监听闲鱼的新消息通知,把通知文本作为触发信号,再通过无障碍服务读取完整会话内容。两种方案可以叠加使用,通知监听负责实时唤醒,界面轮询负责兜底补拉。

3.2 意图分类体系:如何界定“买、问、售后”

意图分类是整个系统中决定用户体验的核心模块。分类过粗会把售后当成购买,造成错误发货;分类过细则模型容易混淆,反而降低准确率。我最终定义了一个五分类体系:

  • BUY:明确表达购买意愿,例如“怎么拍”、“给我来一个”、“多少钱”。
  • ASK_USAGE:询问使用方法/安装问题,例如“win10 能装吗”、“密钥在哪填”。
  • ASK_STOCK:询问库存与发货时效,例如“还有货吗”、“明天能发货吗”。
  • AFTER_SALE:涉及退款、换绑、异常,例如“用不了退钱”、“能换台电脑吗”。
  • OTHER:与交易无关或意图不明,需转人工。

有意思的是,小样本实测中,模型最容易混淆的是 BUY 和 ASK_STOCK。很多买家会先问一句“还有吗”,紧接着下一句就是“给我来一个”。单纯看单句话无法准确判断。所以我把“同一 session 最近 5 条消息拼接后输入模型”,让模型基于上下文做判断。这个改动让 BUY 意图的 F1 值从 0.86 提升到了 0.94。

3.3 提示词设计与结构化输出的工程细节

意图分类通过提示词工程实现,不微调模型。Prompt 设计了三层结构:角色设定、任务规则、输出格式。

你是一个闲鱼虚拟商品交易助手。根据以下对话历史判断买家最新一条消息的意图。 分类标签:BUY(购买)、ASK_USAGE(使用咨询)、ASK_STOCK(库存咨询)、AFTER_SALE(售后)、OTHER(其他)。 规则: 1. 如果买家表达任何购买、拍下、支付的意愿,归类为 BUY。 2. 如果买家在询问使用方法,归类为 ASK_USAGE,即使消息中包含“多少钱”。 3. 如果买家提到退款、异常、无法使用要求解决,归类为 AFTER_SALE。 4. 多条消息结合上下文判断,以最新一条消息为主。 对话历史: {conversation_history} 请只输出 JSON 格式,不要输出其他任何内容: {"intent": "分类标签", "confidence": 0.0-1.0, "reason": "判断理由"}

这里有一个关键细节:结构化输出必须靠格式约束,而不是靠模型自觉。直接让模型“输出 JSON”,它偶尔还是会夹带解释性文字。我用的是 vLLM 的 guided decoding 功能,在采样阶段直接约束输出只能符合 JSON 语法和预期的枚举值,把格式错误的概率压到零。如果你用的是 Ollama,也可以配合format: json参数实现类似效果。

3.4 本地大模型部署选型:从 7B 到 1.5B 的性能取舍

部署方式上我试过两条路线。第一条是调用云端大模型 API(qwen-plus、gpt-4o-mini),推理质量好,但存在两个问题:一是每万次调用的成本对个人卖家来说不是小数目;二是闲鱼消息是敏感的第三方平台数据,出于隐私考量,发送到云端并不是最优解。第二条是本地部署开源模型,这也是我最终采用的方式。

本地模型的选型过程经过了多轮实测。最初用的是 Qwen2.5-7B-Instruct,4bit AWQ 量化后部署在 24GB 显存的消费级显卡上,意图识别准确率最高,单卡并发约 10 QPS,但显存占用达到 9.5GB,且生成速度只到 25 token/s 左右。之后换成了 Qwen2.5-3B-Instruct,准确率下降约 1.5 个百分点,但显存占用降到了 3.8GB,生成速度提升到 65 token/s,已经能保证每个消息 2 秒内返回结果。

最终线上服役的是Qwen2.5-3B-Instruct + vLLM组合。vLLM 的 PagedAttention 机制让长上下文推理的显存利用率高了不少,连续跑了 27 天只重启过一次。如果你没有 GPU,退而求其次用 Ollama 跑 qwen2.5:1.5b 也能完成分类任务,只是准确率会再掉 2~3 个百分点,作为兜底也可以接受。

4. 自动发货执行层:防重复、防超卖、可追溯的工程实践

4.1 幂等设计:同一个订单永远只发一次货

自动发货最严重的故障不是“没发货”,而是“发了两次”。卡密类商品一旦被买家看到,就失去了控制。随便一个技术失误导致二次发送,损失的是实打实的库存。

幂等设计我用了三个层级。第一层是数据库唯一约束,在 delivery_log 表上给 order_id 字段建唯一索引,插入时如果订单号已存在就直接失败。第二层是 Redis 分布式锁,在发货操作前先尝试获取deliver_lock:{order_id}的锁,只有拿到锁的线程才能执行发货,避免并发请求同时通过第一层校验。第三层是状态机校验,只有当前状态是 PAID 才允许发起发货,发货成功后状态更新为 DELIVERED,任何其他路径无法再次触发。

实际编码里,这三层缺一不可。只有约束时,两个进程同时读到待发货状态、同时尝试插入日志,其中一个插入会因唯一索引报错,但此时卡密已经发送出去了;只有锁时,如果服务重启导致锁失效,依然有重复风险。三层叠加才叫真正的幂等。

# 伪代码:发货主流程 def deliver(session_id: str): order = get_order(session_id) if order.status != OrderStatus.PAID: raise InvalidStateError lock_key = f"deliver_lock:{order.order_id}" with redis_client.lock(lock_key, timeout=10): # 再次确认状态,防止其他线程已发货 order = get_order(session_id) if order.status != OrderStatus.PAID: return code = inventory_pool.pop_one(order.goods_id) if code is None: alert_manual("库存不足,订单 %s 等待人工补货" % order.order_id) return send_message(session_id, "您的卡密是:%s" % code) update_order_status(order.order_id, OrderStatus.DELIVERED) record_delivery_log(order.order_id, code)

4.2 库存扣减的原子操作与超卖防护

卡密库存池本质是一个字符串集合。我最初用 SQLite 的SELECT ... WHERE sold = 0 LIMIT 1+ 更新标志位来实现,上线第三天就出现了超卖——原因很简单,UPDATE 语句在事务隔离级别为 READ_COMMITTED 时,两个并发事务可能同时 SELECT 到同一条未售出的卡密。

后来改成了 Redis 的原子操作:卡密集合直接放到 Redis 里,用SPOP命令弹出一条未售出的卡密。SPOP 是原子性的,多个并发请求同时执行时,Redis 内部排队执行,不会出现两条请求弹到同一条记录的情况。每弹出一条卡密,同步写一条 log 到 SQLite 用于审计。

如果你不想引入 Redis,也可以用 SQLite 的UPDATE ... WHERE sold = 0 RETURNING *配合 SERIALIZABLE 隔离级别实现原子扣减,但实测锁竞争在高并发下会让吞吐量掉一个量级。虚拟商品库存量通常只有几百到几千条,SPOP 的 O(1) 复杂度非常合适。

4.3 消息模板与动态渲染

发货消息不是简单的“您好,您的卡密是 X”,模板里最好带上订单信息、使用说明和客服备注,降低买家后续咨询的概率。我用 Jinja2 渲染模板,模板上下文由执行层注入。

Hi {{ buyer_name }}, 您的卡密已自动发货,请复制以下内容: 商品:{{ goods_name }} 卡密:{{ code }} 使用步骤: 1. 打开官网 → 2. 点击激活 → 3. 输入上方卡密 → 4. 确认设备绑定 如有问题请直接留言,人工客服会在 10:00-22:00 在线。

一个经验:模板字数要在 200 字以内,太长的消息在闲鱼聊天窗口显示会折叠。买家看到折叠内容的第一眼,如果没看到卡密本体,很容易产生“客服没回复”的错觉,然后追加消息触发误判。所以我把卡密放在模板的前三分之一处,再加粗标记。

4.4 异常兜底:大模型置信度低时自动转人工

即使准确率到了 95%,剩下的 5% 依然不能被忽略。我的兜底策略很简单:当模型输出的 confidence 低于 0.75,或分类结果是 OTHER 且包含资金相关关键词时,系统不执行任何自动动作,而是把消息推送到企业微信群机器人,由人工接手处理。

这个“宁可漏发、不可错发”的设计很关键。自动漏发一个人工还能补救,但错发一单就是真实资损。上线后统计,每天转移到人工处理的消息在 3~5 条之间,人工确认后大约有 60% 其实是正常购买意图,但多一次人工确认换来的安全边际完全值得。

4.5 会话快照与审计日志

自动发货涉及金钱交易,出问题时必须能够回溯。系统在每次发货动作前,会先把当前会话的完整上下文——所有消息、模型意图分类结果、置信度、库存弹出记录、发送时间——持久化到审计日志表。

这不是数据库触发器自动记录的简单日志,而是一条完整的链路追踪记录。出问题时,按订单号直接查询,能看到“买家在 22:13:05 说‘发我一下’,模型判断为 BUY,置信度 0.92,系统在 22:13:08 弹出了卡密 X”。任何环节的争议,都能在几分钟内定位。这个习惯帮我处理过两次“买家说没收到但实际已发”的纠纷,调出日志后买家当场承认是自己没找到消息。

5. 实测效果、踩坑记录与后续演进思路

5.1 上线后的真实数据

系统完整运行 30 天后的数据表现如下:

指标人工模式自动化模式
日均处理消息数80 条(被重复问题淹没)452 条(含兜底自动回复)
平均响应时长8 分钟(仅限白天)12 秒(7x24 小时)
人工介入率100%11.2%
卡密发货正确率100%(但靠人肉)99.1%(出错均为模型误判后人工兜底)
资损00

最明显的变化是夜间订单不再积压。以前早上醒来第一件事是处理昨晚的十几条“在吗”,现在打开手机看到的是发货日志里整整齐齐的 15 条“已自动发货”。买家体验也好了不少,凌晨拍下后 10 秒内收到卡密,交易完成率提升明显。

5.2 踩坑记录:从服务崩溃到风控降级

必须承认,整个架构也不是一次跑通的。我踩过三个印象深刻的坑:

第一个坑是数据库连接泄漏导致的内存溢出。SQLite 本身是嵌入式数据库,但我用 SQLAlchemy 连接后没有配置连接池回收策略,运行一周后内存占用持续攀升,直到 OOM 崩溃。排查方式是导出 heap profiler 快照,发现大量空闲连接对象堆积。修复方案是给 create_engine 加上pool_pre_ping=Truepool_recycle=3600参数,问题彻底解决。

第二个坑是大模型推理被慢请求阻塞。vLLM 在高并发下如果遇到超长上下文请求,会让同一个 session 内后续请求排队等待。买家连发三条消息时,第三条消息可能会等待前两条推理完成后再排队推理,导致响应时间从 12 秒飙到 30 秒以上。修复方式是把消息处理逻辑改为异步任务队列,不再依赖同步 RPC 请求,前端界面有独立的超时兜底。

第三个坑是商品图更新导致自动化流程失效。接入层通过 UI 控件 id 定位聊天页,有一天闲鱼客户端更新后,MessageList 控件的 resource-id 变了,消息抓取全部失败,系统整整静默了 10 个小时。教训是必须给接入层增加健康检查:如果连续 30 分钟抓取不到任何新消息,自动发送告警通知。后来加了一个每小时跑一次的心跳检测,同时保留通过通知栏服务优先捕获新消息的降级通道。

5.3 模型升级与多商品支持的经验

未来想做的改进集中在两个方向。第一是把单轮意图分类升级为多轮对话管理,让系统能处理“你这个和 XXX 有什么区别”这类带比较性质的复杂问题,这需要引入带记忆的 Agent 框架。第二是把模型从固定部署切换为流式加载,根据会话的活跃度动态加载/释放模型,进一步降低常驻显存占用。

如果这个项目由你来做,我的建议是不要一上来就追求完全的无人化。先跑一个“推荐模式”——系统返回建议动作,人工确认后执行。运行两周积累足够的线上标注数据后,再逐步开放全自动执行。这个迭代路径虽然看起来慢,但每一步都有真实数据兜底,远比一次性写完整个系统保守得多。

5.4 合规与账号安全提醒

最后必须说一句:自动化抢占平台注意力的方案从来就不是官方鼓励的方向。我现在做的这套系统只处理发货环节,不碰任何营销、导流、评价相关的操作,即便如此,也面临着账号被风控限制的理论风险。所以,硬性的建议是:

  • 主号跑自动化,一旦触发异常立即下线,配备一个小号做验证测试。
  • 接入层只读、不写、不批量操作,模拟自然人的访问频率。
  • 对话里涉及资金、退款或投诉的字眼时,不做自动化处理,直接转人工。

终归,自动化工具的意义是让你从重复劳动里解脱出来,把精力放在真正需要人判断的事情上。我在跑这套系统的第二周,就把每天省下来的两小时拿去做商品文案和客户回访了,那才是交易里真正产生差异化的地方。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询