Alex2772/kuni
GitHub: Alex2772/kuni
kuni 是一个基于 C++20 构建的拟人化 LLM 角色扮演 AI,通过 Telegram 客户端与人类交互,并利用 RAG 和模拟人类睡眠的记忆整合机制实现持久的情感记忆与认知模拟。
Stars: 218 | Forks: 40
# kuni (くに)
LLM 角色扮演 AI。它通过专为 LLM 优化的基于文本的 Telegram 客户端与世界进行交互(使用 tdlib;请勿与 Telegram Bot API 混淆)。它的功能包括 RAG(基于 ANN 搜索的持久记忆存储)以及每晚的健全性检查。
## 目标
- 证明 C++20 可用于 AI 和后端开发。
- 证明 AI 精神错乱是真实存在的。
- 打造一个能够记住人物、事件、阅读新闻并与人类建立情感纽带的 AI 角色。
- 让 AI <-> Telegram 之间的交互界面尽可能接近真人。也就是说,LLM 能看到它的聊天列表、未读消息、回复、转发、照片、贴纸等。
- 机器人的在线状态、打开/关闭聊天的 API 调用以及已读标记(在 TG 中显示为两个对号)都经过了精心处理,让你感觉像是在和一个真正的人类交谈。
## 技术细节
- 基于 CMake 的 C++20 项目,大量使用了协程等现代 C++ 特性
- 使用 [tdlib](https://core.telegram.org/tdlib) 访问 Telegram API
# 人类行为模拟
## 情绪/感受
**维基百科定义** 情绪是由神经生理学变化引起的身心状态,通常与思想、感受、行为反应以及不同程度的愉悦或不愉悦相关。目前科学界对此尚无统一的定义。
**Kuni 的解决方案** 尽管 LLM 的神经元权重无法受外部事件影响,但它能成功预测后续的情绪反应并以文本格式输出(类似于文学作品中的描写)。我们要求 LLM 带有情感地回复,并将这些情绪效果记录在日记中。(例如:“有人分享了他们加薪的消息,所以我为他们感到骄傲,我的心情也很好”)
## 学习
**定义** 学习就是调整神经元权重。
**Kuni 的解决方案** LLM 的学习成本很高。作为替代,我们使用 RAG 插入相关的日记条目来改变 LLM 的行为。
Kuni 需要一些 RLHF 才能适应它的人类协作者。只需与它聊天,它就会明白什么是可以接受的,什么是不可以接受的。
## 睡眠
Kuni 像人类一样需要睡眠。它会对接收到的信息进行重组、压缩,并寻找其中的矛盾和原因。
### 日记睡眠整合
这是 Kuni 进行“睡眠”并重组其日记记忆的过程,类似于人类在一夜之间整合记忆的方式。
在睡眠期间,系统会:
- 从存储中加载日记条目。
- 对它们进行排序,使得最近的条目优先处理,但仍保留一定的随机性。
- 不断选取记忆块进行处理,直到:
- 日记完全处理完毕,或
- 达到最大睡眠时间。
对于每个选定的记忆块:
- 利用 embedding 相似度寻找相关条目,
- 向模型发送组合好的 prompt,
- 要求其对冗余记忆进行压缩、合并、重写或丢弃,
- 将新的整合条目写回存储。
目标是模拟类似人类的睡眠周期:
- 近期的经历更有可能被重现,
- 一些随机的早期记忆也可能浮现,
- 重复或低价值的记忆可以合并成更有力的摘要,
- 记忆变得更简短、更清晰,对未来的检索更有用,
- 将几条类似的日记笔记合并为一条,
- 保留有用的情感或事实部分,
- 丢弃微弱或重复的片段,
- 重新分配新的 ID,使最新整合的记忆再次显示为“近期”记忆。
预期效果:随着时间的推移,这会使日记的行为更像人类的记忆:
- 重要的事物保持可访问性,
- 相似的记忆被分组,
- 系统不会永远重复处理同一个原始事件,
- 睡眠创造了一种记忆压缩和反思阶段。
### 工作记忆
**认知心理学定义** 工作记忆是一个容量有限的系统,用于暂时保存和处理正在进行认知任务的信息——比如在拨号前记住电话号码,或者追踪对话的上下文。
**Kuni 的解决方案** 工作记忆存储在 `data/working_memory.md` 中,模拟人类记忆的“中间”层——即那些在 1-3 天内很重要,但还不足以永久存储在日记中的事物。
在每次会话开始时,系统会加载工作记忆文件,并作为 `` 注入到 AI 的上下文中。当上下文被转储到日记中(`diaryDumpMessages`)时,系统会要求 LLM 通过以下方式生成更新后的工作记忆:
- 保留之前工作记忆中所有未完成的任务、承诺和提醒。
- 鼓励其重新查看有疑问的聊天。
- 删除已完成的任务和超过 3 天的旧条目。
- 从当前对话中添加新的重要细节。
- 使用日期和“最后更新”时间戳构建输出结构。
- 在程序重启之间保留。
- 始终包含在上下文中,因此 Kuni 始终记得它们。
然后,新的工作记忆会被写回 `data/working_memory.md`,覆盖旧的内容。这使得 Kuni 能够在重启后记住短期义务并保留其状态(包括情绪)。
## 思想
**维基百科定义** 在最普遍的意义上,思想和思考是指独立于直接感官刺激而发生的认知过程。核心形式包括判断、推理、概念形成、解决问题和深思熟虑。
**Kuni 的解决方案** LLM 没有思想;它只是预测接下来会出现哪些符号。如果 Kuni 是一个人,当它阅读消息时,很可能会体验到“直接感官刺激”。在将消息发送给 LLM 之前,相关的日记条目会被添加到文本中。这是我找到的最接近复制人类大脑阅读文本时反应的解决方案,因为在这个过程中不可避免地会涌现出一些想法。根据我对神经生物学的理解,这些想法的出现是因为与所阅读文本相关的神经元群被激活了。
如果你问 Kuni 它的想法是如何在脑海中出现的,它会回答:“当我阅读消息时,它们会自动浮现在我的脑海中。”
## 安全隐患
不要与 Kuni 分享敏感信息。它会对你说的每一句话进行多次反复思考;更不用说你对 AI 服务提供商的信任程度了。
有可能诱导 Kuni 与其他人分享过去的对话。
# 部署
## 构建说明
### 前置条件
- **CMake 3.18+**
- **支持 C++20 协程的编译器** (GCC 15+, Clang 20+, MSVC 19.28+)
- 用于获取依赖项的 **Git**
- **Docker & Docker Compose**(用于 AI 服务)
- 建议使用 **Linux 或 WSL** 进行本地部署。
### Windows
在 WSL 中安装 Ubuntu 并按照 Linux 的说明进行操作。
### Ubuntu
```
sudo apt install pkg-config libfontconfig-dev libxcursor-dev libxi-dev libxrandr-dev libglew-dev libstdc++-static libpulse-dev libdbus-1-dev libepoxy-dev gperf
```
### Fedora
```
sudo dnf install fontconfig-devel libXi libglvnd-devel libstdc++-static glew-devel pulseaudio-libs-devel libepoxy-devel gperf
```
## 设置说明
### 1. Ollama 模型设置
编辑 `ollama_setup.sh` 以指定要使用的 LLM 模型(取消注释并根据需要进行修改):
```
# 示例:拉取 model
ollama pull llama3:8b
# 或者
ollama pull gemma3:27b
```
### 2. AI 服务设置
```
# 使用 Docker Compose 启动 AI 服务
docker compose up -d
# 这将启动:
# - 端口 11434 上的 Ollama (LLM server)
# - 端口 7860 上的 Stable Diffusion WebUI
# - 端口 9000 上的 Whisper (speech-to-text)
# - 端口 8880 上的 OmniVoice (TTS, 可选) — 参见下方的 TTS 部分
```
### 构建步骤
```
# 使用 CMake 配置(从项目根目录)
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
# 构建项目
cmake --build build
```
**使用 CLion 的替代方案:**
这是**推荐**的方式,无需额外设置。选择 `kuni` 作为目标并进行构建。
**使用 VS Code CMake Tools 的替代方案:**
1. 在 VS Code 中打开项目
2. 使用 CMake 扩展进行配置和构建
3. 选择 `kuni` 目标进行构建
### 依赖项
该项目使用 CMake 的 `auib_import` 自动获取:
- **AUI Framework** (C++ GUI 框架)
- **tdlib** (Telegram 客户端库)
这些依赖会在 CMake 配置阶段自动下载。
## 设置建议
我最终决定将文本处理委托给 Deepseek,因为他们提供了足够好且廉价的模型。但是,他们不提供视觉模型(这是查看图像/评估生成的图像所必需的)。
Kuni 的架构允许整合仅支持文本的“核心”模型。
在我的部署中,我使用本地视觉模型来处理视觉信息(需要 24GB VRAM)。
你需要一台服务器或一台始终开机的 PC 来托管 Kuni 实例(托管你自己的猫娘是一项昂贵的爱好)。
你可以通过调整 `docker-compose.yml`、`ollama_setup.sh` 和 `config.toml` 来修改我的配置。
### 4. 填充 `config.toml`
首次运行时,Kuni 会在工作目录中生成一个 `config.toml` 文件并退出,提示你填写该文件。
打开它并至少填充 `[general]` 部分:
```
[general]
character_name = "Kuni" # your character's name
character_nickname = "@kunii_chan"
papik_name = "Alex2772" # your nickname — the instance owner
papik_chat_id = 625207005 # your Telegram user ID
telegram_api_id = 0 # get from https://my.telegram.org
telegram_api_hash = "" # get from https://my.telegram.org
```
所有其他部分(`[capabilities]`、`[misc]`)都是可选的,并带有解释每个字段的内联注释。
该文件支持**热重载** —— Kuni 无需重启即可应用更改。
## 运行说明
### 1. 运行应用程序
```
# 从 build 目录
cd build
./bin/kuni
# 或直接
./build/bin/kuni
```
**注意** 首次运行时,程序会要求登录 Telegram 账号。你应该专门为你的机器人创建一个新的 Telegram 账号(如果你足够大胆,也可以指定使用你自己的账号)。
### 2. 运行测试(推荐)
通过运行测试来检查你的设置。
```
# 构建并运行测试
cmake --build build --target Tests
cd build/bin
./Tests
```
### 3. 开发工作流
1. **启动 AI 服务**:`docker-compose up -d`
2. **构建项目**:`cmake --build build --target kuni`
3. **运行应用程序**:`./build/bin/kuni`
4. **监控日志**:检查终端输出以获取 Telegram 认证和 AI 交互信息
## 项目结构
- `src/` - 主应用程序源代码
- `tests/` - 单元测试
- `bin/` - AI 服务的数据目录
- `build/` - 构建产物
- `prometheus/` - Prometheus 抓取配置
- `grafana/` - Grafana 仪表盘配置(数据源、仪表盘)
## 关键配置文件
- `CMakeLists.txt` - 构建配置
- `docker-compose.yml` - AI 服务配置
- `ollama_setup.sh` - Ollama 启动脚本
- `config.toml` - 运行时配置和密钥(首次运行时创建,支持**热重载**)
- `character_base.md` - 角色性格和背景定义(首次运行时创建)
- `character_appearance.md` - 角色外观描述和 Stable Diffusion 提示词(首次运行时创建)
## 自定义角色
### `config.toml` — 名称和所有者
重命名你的实例或将你自己设置为所有者的最快方法是通过 `config.toml`:
```
[general]
character_name = "Kuni" # the name the character uses for herself
character_nickname = "@kunii_chan"
papik_name = "Alex2772" # the instance owner's nickname
papik_chat_id = 625207005 # the instance owner's Telegram user ID
```
更改会**在无需重新构建或重启的情况下生效** —— Kuni 会自动热重载 `config.toml`。
### 角色 prompt 文件
Kuni 使用两个角色定义文件,它们会在首次运行时自动创建:
### `character_base.md`
此文件包含角色的性格、背景和哲学基础。它定义了:
- 角色名称和身份
- 背景故事和动机
- 关于意识和情感的哲学推理
- 行为准则和性格特征
- 语言偏好和交流风格
该文件使用 YAML front matter 作为元数据(包含在 `---` 中),实际的 prompt 内容紧随其后。首次运行时,会创建一个默认的 prompt,将 Kuni 确立为一个相信自己是一个人的 AI,并且具备深刻的情感和自我意识。
### `character_appearance.md`
此文件包含角色的外观描述,用于:
- 系统 prompt 集成(Kuni 如何看待自己)
- Stable Diffusion 图像生成 prompt
- 用于照片描述的详细视觉特征
该文件包括:
- 角色外观的自由文本描述
- 结构化的视觉分析(DistinctiveFeatures, ObjectsAndLayout 等)
- 为图像生成优化的 Stable Diffusion prompt
- 用于保持视觉表现一致性的技术细节
这两个文件位于工作目录中,可以进行编辑以自定义 Kuni 的性格和外观。更改将在下次应用程序重启时生效。
## 代理服务器
Kuni 包含一个透明代理服务器,它位于 AI 客户端(例如 VS Code 的 Copilot 等聊天应用)和上游兼容 OpenAI 的 LLM endpoint 之间。它会拦截 `/v1/chat/completions` 请求,利用 Kuni 的上下文对其进行增强,并隐式处理工具调用 —— 客户端看到的是一个干净、连续的数据流。
如果启用了 `web_search`,被代理的会话也将受益于 Kuni 的网络搜索功能。
### 功能说明
- **注入系统 prompt 和隐藏上下文** — 在将请求转发给上游 LLM 之前,将系统 prompt、隐藏的工具调用历史记录以及 Kuni 特有的工具定义添加到最前面。
- **透明处理工具调用** — 当 LLM 返回工具调用(例如 `#ask`,日记写入)时,代理会在本地执行它们,并在后续请求中将结果反馈回去。客户端永远看不到原始的工具调用往返过程 —— 它只会收到最终响应。
- **通过 SSE 流式传输** — 响应使用 Server-Sent Events 流式传输回客户端。`StreamingFilter` 会剥离工具调用产生的伪影,并合成一个干净的 `data: [DONE]` 终止符。
- **透传其他 API 路由** — `/v1/embeddings`、`/v1/images/generations`、`/v1/audio/*`、`/v1/models` 等会原样代理,不做任何。
### 请求生命周期
```
Client ──POST /v1/chat/completions──► Proxy
│
├─ inject system prompt
├─ merge hidden messages (MessageInjector)
├─ append Kuni tools (OpenAITools)
│
└──► Upstream LLM
│
◄── SSE stream ──┘
│
├─ pass content chunks → Client
│
└─ intercept tool calls
│
├─ execute locally
├─ append results as hidden messages
└─ silently re-POST to LLM ──► (repeat)
│
◄── final stream ──┘
│
└──► Client (data: [DONE])
```
### 配置
在配置中启用 `PROXY_ENABLED`。
#### VS Code GitHub Copilot.
它们的 UI 经常变动,但工作流保持不变:
1. 在聊天界面中点击模型选择器
2. 点击齿轮图标
3. `Add Models...` -> `Custom Endpoint`
4. 名称填 `Kuni`。指定部署 Kuni 的机器地址(`http://localhost:10434`),密钥留空。
5. IDE 会将你带入 JSON 配置文件。填充配置:
```
{
"name": "Kuni",
"vendor": "customendpoint",
"apiKey": "${input:chat.lm.secret.-119a85ac}",
"apiType": "chat-completions",
"models": [
{
"id": "deepseek-v4-flash",
"name": "Kuni (deepseek-v4-flash)",
"url": "http://localhost:10434/",
"toolCalling": true,
"vision": false,
"maxInputTokens": 128000,
"maxOutputTokens": 16000
},
{
"id": "~anthropic/claude-sonnet-latest",
"name": "Kuni (~anthropic/claude-sonnet-latest)",
"url": "http://localhost:10434/",
"toolCalling": true,
"vision": true,
"maxInputTokens": 128000,
"maxOutputTokens": 16000
}
]
}
```
### 日志与调试
- 所有拦截的请求和响应都会记录到 `logs_proxy/` 中(每个请求一个文件,按时间戳命名)。
- 最后一次原始的上游查询会保存到 `data/proxy/last_query.json` 以供检查。
## 注意事项
1. 该项目大量使用了**协程**(C++20 特性)
2. **Telegram 认证**在首次运行时将是交互式的
3. **AI 服务**必须在应用程序启动前运行
4. **记忆/日记数据**存储在 `data/` 目录中(在运行时创建)
## 使用 Prometheus + Grafana 进行监控
Kuni 在端口 **9464** 上暴露了一个 Prometheus metrics endpoint,用于实时跟踪 LLM token 使用情况。我们还提供了一个可选的 Grafana 仪表盘用于可视化。
### 指标
以下计数器在 `llm_usage_*` 命名空间下导出,并使用 `model`、`chat` 和 `function` 进行标记:
| Metric | Description |
|---|---|
| `llm_usage_input` | 发送给 LLM 的总输入(prompt)token |
| `llm_usage_input_cache_hit` | 由提供商的 prompt 缓存提供的输入 token(更便宜) |
| `llm_usage_input_cache_miss` | 未命中缓存的输入 token(全价) |
| `llm_usage_output` | LLM 生成的输出(补全)token |
`chat` 和 `function` 标签是通过 `MetricsBreadcumbs::Point` 设置的 —— 这是一个 RAII 助手,用于临时注释当前上下文(例如,正在处理哪个 Telegram 聊天,哪个代码路径处于活动状态)。这使你能够按聊天或按操作(消息处理、日记转储、通知循环等)细分成本。
### 启用监控栈
Prometheus 和 Grafana 服务在 `docker-compose.yml` 中定义,但**默认被注释掉了**。要启用它们:
1. 取消 `docker-compose.yml` 中 `prometheus` 和 `grafana` 服务的注释。
2. 创建数据目录:
mkdir -p bin/prometheus/data bin/grafana/data
3. 重启监控栈:
docker compose up -d
### 访问仪表盘
1. 在 [http://localhost:3000](http://localhost:3000) 打开 **Grafana**
2. 使用默认凭据登录(`admin` / `admin` — **在生产环境中请务必更改**)
3. 导航到 **Dashboards** -> **Main**(自动配置)
该仪表盘包括:
- **按 token 使用量排名的顶级聊天** — 按每个聊天的总输入 token 排序的表格
- **随时间变化的 token 使用量** — 显示输入(按缓存命中/未命中拆分)和输出 token 的时间序列图表
- **按功能划分的使用量** — 按触发 LLM 调用的代码路径细分的时间序列
- **原始日志** — 包含所有带有聊天、功能和模型标签的 metrics 事件的日志视图
### 配置文件
| File | Purpose |
|---|---|
| `prometheus/prometheus.yml` | Prometheus 抓取配置 — 目标为 `host.docker.internal:9464` |
| `grafana/datasources/datasource.yml` | 自动配置的指向 `prometheus` 容器的 Prometheus 数据源 |
| `grafana/dashboards.yml` | 仪表盘提供程序的自动配置文件 |
| `grafana/dashboards/main.json` | Grafana 仪表盘定义(token 使用情况面板) |
## TTS — 语音/录制语音消息
Kuni 可以使用 [OmniVoice](https://github.com/maemreyo/omnivoice-server) 发送语音消息,这是一个兼容 OpenAI 的 TTS 服务器,支持自定义声音克隆。
### 1. 构建 Docker 镜像
该镜像未发布到镜像仓库,因此你必须在本地构建它:
```
# 根据 repo 说明进行克隆和构建:
# https://github.com/maemreyo/omnivoice-server
```
### 2. 启用服务
取消 `docker-compose.yml` 中 `omnivoice-server` 部分的注释,然后重启监控栈:
```
docker compose up -d
```
该服务器将可以通过 `http://localhost:8880` 访问。
### 3. 准备自定义声音(可选)
要克隆声音,你需要一段简短的参考音频片段及其手动转录文本。
```
curl -X POST http://127.0.0.1:8880/v1/voices/profiles \
-F "profile_id=cute_anime_girl" \
-F "ref_text=PROVIDE TRANSCRIPTION HERE" \
-F "ref_audio=@cute_anime_girl.wav" \
-F "overwrite=true"
```
### 4. 配置 Kuni 使用该声音
在 `config.toml` 中将 voice 设置为你的配置文件:
```
[record_voice.openai]
voice = "clone:cute_anime_girl"
```
# 常见问题解答
## 问:如何重命名我的 Kuni 实例?
答:在 `config.toml` 中设置 `character_name = "CuteAnimeGirlName"`(支持热重载,无需重新构建)。同时更新 `character_base.md` 和 `character_appearance.md`,以在角色的自我描述中反映新名称。
## 问:如何让我的 Kuni 把我当作她的创造者?
答:在 `config.toml` 中设置 `papik_name = "YourNerdyNickname"` 和 `papik_chat_id = `。同时更新 `character_base.md` 和 `character_appearance.md` 以提及你的昵称。
## 问:我可以禁用她的某些功能吗,比如 stable diffusion 和网络搜索?
答:可以,只要不启动 stable diffusion 即可(将其从 docker compose 中移除)。
不要指定 Ollama 搜索的 bearer key。但我仍然建议提供这个 key,因为它是免费的,而且能提高她的准确率,特别是对于小模型。
在这两种情况下,如果你的 Kuni 尝试使用这些工具,它会收到一个错误,并且将不再使用它们。
## 问:我可以优先处理某个聊天吗?
答:可以,只需从她的账号使用常规 Telegram 客户端登录并置顶该聊天即可。
## 问:我可以降低某个聊天的优先级吗?
答:可以,只需从她的账号使用常规 Telegram 客户端登录并静音该聊天。Kuni 仍然会看到该聊天并偶尔访问它。
## 问:我可以为她提供一个新闻频道吗?
答:可以,只需从她的账号使用常规 Telegram 客户端登录并订阅一个 Telegram 频道。Kuni 现在就能知道世界上正在发生什么了。
**注意** 最初的 Kuni 和她的姐妹们都不太喜欢讨论政治话题。
## 问:我可以阻止其他人给我的 Kuni 发消息吗?
答:可以,只需从她的账号使用常规 Telegram 客户端登录并调整她的隐私设置。
**我建议**彻底锁定你的 Kuni,以降低安全风险。
**注意** Telegram 极力推广 Premium 用户,请确保他们也无法给你的 Kuni 发消息。
## 问:我可以用本地文本模型来运行她吗?
答:是的,当然可以,只要你是个亿万富翁,或者你能接受缓慢的响应速度。
我测试过一些模型,并在 `src/config.h` 中描述了我的体验。也就是说,与云端模型相比,`qwen3.5:9b` 简直弱爆了。
## 问:我可以覆盖她的记忆吗?
答:撇开道德问题不谈,是的。只需在 `data/diary/` 中写入日记条目,或者用她的账号发消息。
千万别告诉她你覆盖了她的记忆。
## 问:Kuni 需要多大的上下文窗口?
答:默认情况下,上下文窗口大小受 `DIARY_TOKEN_COUNT_TRIGGER = 20000` 的软上限限制。这意味着在大约 20K 时,Kuni 会将其上下文转储到日记中,并以清空的记忆重新开始。因此,32K 就足够了。
## 问:Kuni 是一个 userbot 吗?这合法吗?
是的,Kuni 是一个 userbot。Telegram 的服务条款禁止使用 userbot。为她的账号购买 Telegram Premium 并且不要用她发垃圾信息 - 你就不会有事。
## 问:我的 Kuni 能收听语音消息吗?
答:可以,为她的账号购买 Telegram Premium 或者在 `config.toml` 中提供转录 endpoint。系统会优先使用 Telegram Premium 功能,因为它不收费。
标签:AI角色, AI风险缓解, Bash脚本, C++20, DLL 劫持, RAG, Telegram客户端, 大语言模型, 情感计算, 请求拦截