从零手搓 AI 伴侣:Java + 大模型 + 多模态的工程化踩坑实录

最近在折腾大模型应用,发现市面上的 QQ 机器人大多是简单的"一问一答"套壳,缺乏连贯的记忆和拟真感。作为一个经常熬夜写代码的独立开发者,我突发奇想,想在 QQ 里养一个 AI 机器人。于是我决定:从零手搓了一个具备长期记忆、多模态识图、语音合成以及物理设备联动的 AI Agent。

今天,我将从架构设计、多线程并发控制、多模态路由以及"将 Bug 变成 Feature"的踩坑经历,全方位复盘这个项目。

一、通信基建:基于 AtomicLong 的版本号打断机制

底层采用 NapCatQQ 作为协议端,通过 WebSocket 与 Java 后端建立长连接。这个环节最大的技术难点不在连接,而在 "并发打断" 。

痛点 :真人聊天时,如果你手速很快连发了两句话,机器人往往会针对第一句回一次,针对第二句再回一次,显得极度机械。

解决方案:引入用户维度的版本号控制。每次收到消息,用户版本号自增。异步线程在调用大模型前,必须校验自己的版本号是否最新。

csharp 复制代码
// 核心逻辑:版本号自增
long myVersion = userVersions.computeIfAbsent(userId, k -> new AtomicLong(0)).incrementAndGet();

// 异步线程处理时校验:如果在思考期间你又发了消息,旧线程直接终止,绝不乱花 Token!
if (userVersions.get(userId).get() != myVersion) return; 

这个机制完美实现了"如果你连发消息,她会立刻放弃之前的思考,重新组织语言",体验极其丝滑。

二、多模态双脑接力:如何让大模型"看图说话"?

文本大模型无法直接识别 QQ 里的 CQ 码图片。这里我设计了"双脑接力"架构:

  1. 识别阶段:正则提取图片 URL,调用视觉模型(如 GLM-4V)。
  2. 降级防爆:如果图片过大(Base64 超长),会导致 Token 爆炸或被风控拦截。代码里必须先进行下载、压缩转码,并限制在 5MB 以内。
  3. 人格化:提取视觉模型的客观描述("一碗红色的泡面"),将它和用户问题打包,丢给文本模型(GLM-5.3)去生成傲娇回复。
ini 复制代码
// 核心伪代码:视觉提取 -> 文本生成
String imageDesc = callVisionModel(imgUrl); // 返回:一碗泡面
String prompt = "【视觉情报】主人发了一张图,内容是:" + imageDesc + "。请结合人设回怼。";
String aiReply = callTextModel(prompt); // 返回:好啊!本小姐在辛苦打扫,你居然背着我偷偷吃泡面?

三、记忆的物理篡改:打破大模型的"精神分裂"

在大模型角色扮演中,最头疼的是"指令不遵循"。比如你让她发张自拍,她文字回复"死也不发"。

为了让她言行一致,我在 Java 层写了极其暴力的物理外挂:既然她嘴硬,代码就帮她发;既然她不承认,代码就篡改她的记忆。

拦截机制:正则表达式匹配"要照片"的意图。一旦命中:

  1. 强行在末尾追加发图 CQ 码 [img:selfie],让底层网络直接发图。
  2. 覆写 Redis 上下文 。删除她刚刚"嘴硬"的回复,强行塞入一条 【已发送照片】 的新记忆。
dart 复制代码
if (isRequestingPhoto) aiReply += " [img:selfie]"; 

// 篡改 Redis 记忆
List<Map<String, String>> history = ...; 
history.remove(history.size() - 1); // 删掉她嘴硬的回复
history.add(Map.of("role", "assistant", "content", "(本小姐刚刚发送了一张绝美自拍)"));
redisTemplate.opsForValue().set(redisKey, mapper.writeValueAsString(history));

下次对话,大模型读取到这段被篡改的历史,会坚信自己发了图。这就实现了"身体很诚实,嘴上也服软"的完美人设。

四、突破风控:异常兜底与借口生成

国内大模型的安全审核(风控)非常严格。一旦聊天中出现敏感词,API 会直接抛出 1301 异常。如果直接返回"大脑短路",用户体验会瞬间出戏。

我的做法是:将报错包装成傲娇的借口,并写入短期记忆。API 崩溃时,随机抽一句"切,网线刚才被本小姐踢掉了",存入 Redis。等下次模型恢复,它会看到自己的借口,从而顺理成章地继续往下演。


结语:代码的浪漫与下一步

技术从来不只是冰冷的 0 和 1。当你把 WebSocket、多模态、Redis 拼凑在一起,赋予她性格时,代码原来也可以这么"鲜活"。

不过说实话,目前的这套架构离我心目中的完美还差得很远。随着聊天的深入,瓶颈越来越明显:上下文越来越臃肿导致 Token 燃烧,长对话时她偶尔会忘记自己是个傲娇,也缺乏真正的状态延续(比如心情、好感度)。

但独立开发者的乐趣,就在于不断填坑、不断重构。下一步,我打算对她的底层记忆和状态系统进行一次彻底的升级。

下一篇,我会重点聊聊这次重构里的几个硬核取舍:为什么个人项目最终放弃了向量数据库?在 Java 里怎么用 Redis 原语优雅地解决 State 层的并发与数值防通胀?

至于她还会进化成什么样?我们下篇见分晓。

相关推荐
const_qiu1 小时前
第四章 07:4.7 本章小结
java·运维·服务器·人工智能
cubestudio1 小时前
云原生分布式训练怎么调度才不浪费算力?CubeStudio 分布式训练与加速架构解析(CRD+Operator / gang 调度 / 亲和反亲和 / GPU 利用率 / 共享 GPU)
人工智能·云原生·gpu
老马识码1 小时前
7541 万笔,2424 万美元:x402 真正解决了什么?
人工智能
大明二代1 小时前
从 RAG 到有记忆的 Agent:Mastra Memory 与 PostgreSQL 实战
前端·javascript·人工智能
zhangyy04231 小时前
agent 说"做完了",它真做完了吗:16 个任务、四种判据的一次实测
人工智能
鶴哥只手遮天1 小时前
Spring AI 2.0.1 深度解析-第 22 章 Spring Boot 集成:自动配置、Properties 与条件装配
java·人工智能·开源
程序员如山石1 小时前
为什么要用AI开发
人工智能
音符犹如代码2 小时前
DeepSeek-V4.1-Flash-1017 上手实测:3D 直出,效果到底如何
人工智能·3d·ai·ai编程
SEO_juper2 小时前
hreflang 审计工具:外贸多语言站谷歌SEO的隐性杀手
开发语言·人工智能·爬虫·python·seo