回到小窝里的作品

Nyalume

让 AI 角色,从聊天窗口走到桌面。

对话、记忆、实时 3D 和桌面互动,都在这个项目里慢慢长大。希望做出一个能交流、有回应,也能在日常里陪一会儿的角色。

持续打磨中网页 / Windows 桌面项目源码

网页对话需登录并自备 API Key;Unity 桌宠在桌面版中体验。

同一个角色项目,
两种相遇的方式。

对话里,留下交流和记忆。
桌面上,多了一个能回应操作的 3D 角色。

能聊,也能在桌面上陪一会儿

把喜欢的角色做成可以互动的东西,是这个项目一直在探索的方向。每个入口有自己的使用场景,也有清楚的能力边界。

AI 对话与记忆

网页端支持账号、会话与流式回复,围绕用户保存对话和陪伴记忆;桌面端则在本地运行,保留自己的模型配置和工具能力。

对话体验里有许多需要认真处理的小事:回复中断怎么办,哪些内容应该留下,以及怎样让下一次交流接得上。

Unity 实时 3D 桌宠

桌面端已有 Unity 角色运行与桥接实现,包含摸头、戳脸、握手和移动等鼠标交互。角色的渲染、动作与操作反馈一起构成桌面体验。

模型、动作和交互表现还在持续调整。不同角色的兼容性与最终效果,需要分别验证。

角色行为与主动互动

项目已有陪伴状态、主动话题和桌面互动相关实现。目前继续打磨的,是这些行为之间的节奏、连续性,以及对用户打扰程度的控制。

希望角色逐渐形成自己的行为逻辑、状态与习惯。这仍是探索方向,距离自然的长期陪伴还有事情要做。

可爱背后的工程实现

技术栈负责把体验撑起来。下面这些,是本地代码里已经能找到的实现。

网页后端

流式对话
用 SSE 将模型增量响应送到浏览器;校验流结束状态,遇到超时或中断时返回明确错误。
账号与数据
PostgreSQL 保存会话和陪伴记忆。查询同时校验资源 ID 与用户 ID,密码采用带随机盐的 scrypt 哈希。
调用边界
服务商地址白名单、请求大小限制与账号配额共同约束调用。用户 Key 用于转发,不写入服务端数据库。
部署实践
Docker Compose 编排 API、数据库与 Caddy;数据库不发布到宿主机公网端口,Caddy 承担 HTTPS 与反向代理。

桌面角色

Python × Unity
Python 侧通过共享内存桥向 Unity 发送动作与状态,读取角色反馈;桌面窗口负责呈现角色与交互界面。
本地与公开服务
桌面能力运行在用户设备上;公开网页服务隔离账号数据,不开放本地文件与代码执行工具。两端的边界分别设计。

把几个实现拆开看看

从现有源码整理的三个问题。字都认识,放到一起为什么还得想一会儿?

流式回复:字都出来了,然后呢?接口与通信收到最后一个字,不一定等于请求成功。

Nyalume 用 SSE 发送 delta、error 和 done 等事件。上游返回文字后,还要检查结束标记与 finish_reason;超时、截断和不完整的流都不能当作成功。

只有确认回复完整后,才写入本次用户消息和助手回复,再发送完成事件。这样能减少半截回答进入聊天记录的情况。

对应实现:cloud_service/web.py ↗
一个会话 ID,为什么还要再查用户 ID?数据与权限数据属于谁,要由后端来确认。

资源 ID 不应替代权限校验。网页端读取、修改和删除会话时,使用 id 与 user_id 共同约束查询条件;陪伴记忆也按用户单独保存。

数据库用外键表达消息与会话的关系,并为按用户和时间检索会话、按会话读取消息建立索引。应用校验权限,数据库维护结构,两层各自负责。

对应实现:web_schema.sql ↗
为什么网页小窝没有桌面端的工具权限?服务与工程入口不同,信任边界也不同。

公开网页服务面向多个账号,桌面助手则运行在用户自己的设备上。因此,网页模块不导入桌面 Agent,也不提供读写文件、执行代码等工具调用。

这条边界落实在模块依赖、模型请求和响应校验中:不发送工具定义,遇到上游工具调用也会拒绝。先限制服务能做什么,再讨论它可以做得多聪明。

对应实现:cloud_service/web.py ↗

做到哪了,还想做什么

已有实现
网页账号与流式对话、会话数据隔离、陪伴记忆,以及 Unity 角色桥接与鼠标互动。
持续迭代
角色响应、模型与动作兼容、主动互动的节奏,以及各模块之间的体验衔接。
探索方向
更连贯的角色状态、更自然的主动行为,让陪伴逐渐有自己的节奏。

以上描述对应当前本地实现与开发方向;具体发行版本的功能,以仓库和发行说明为准。

慢慢写,慢慢改。希望下次见面的时候,又多做好了一点。