Files
NewWPSBot/.past_tasks/2025-10-29_3_ai_chat.md
2025-11-06 16:26:07 +08:00

428 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 背景
文件名2025-10-29_3_ai_chat.md
创建于2025-10-29_23:32:40
创建者user
主分支main
任务分支task/ai_chat_2025-10-29_3
Yolo模式Off
# 任务描述
在本项目中新增一个AI对话功能使用llama_index构建服务商为本地部署的ollama。
这个智能体将拥有足够长的上下文超过30轮对话历史能够同时与不同的用户展开交流。例如用户A提问后用户B进行补充智能体将通过时间间隔判断机制来决定何时进行回答。
## 核心需求
1. **显式指令触发**:使用 `.ai <问题>` 指令触发AI对话
2. **配置指令**:使用 `.aiconfig` 指令配置Ollama服务地址、端口和模型名称
3. **时间间隔判断**智能体通过时间间隔判断是否需要回答固定10秒等待窗口
4. **长上下文管理**保留超过30轮对话历史
5. **多用户对话支持**同一chat_id下不同用户的消息能够被正确识别和处理
## 技术方案决策(已确定)
1. **延迟任务机制**:使用 asyncio 的延迟任务(方案三)
- 每个 chat_id 维护独立的延迟任务句柄
- 使用全局字典存储任务映射
- 收到新消息时取消旧任务并创建新任务
2. **上下文管理**:使用 llama_index 的 ChatMemoryBuffer策略A
- 设置足够的 token_limit 确保保留30+轮对话
- 按 chat_id 独立维护 ChatEngine 实例
3. **多用户识别**:消息角色映射 + 系统提示方案C
- 将不同用户映射为不同角色(如"用户1"、"用户2"
- 在系统提示中明确告知存在多用户场景
- ChatMemoryBuffer 中使用角色区分不同用户
4. **等待窗口**固定10秒窗口变体1
- 收到消息后等待10秒
- 等待期间有新消息则重新计时
5. **配置管理**使用单独的JSON文件
- 配置存储在 `data/ai_config.json`
- 全局单一配置服务器级别非chat级别
- 通过 `.aiconfig` 指令修改配置并保存到文件
# 项目概览
## 项目结构
```
WPSBot/
├── app.py # FastAPI主应用
├── config.py # 配置管理
├── core/ # 核心模块
│ ├── database.py # 数据库操作
│ ├── models.py # 数据模型
│ └── middleware.py # 中间件
├── routers/ # 路由模块
│ ├── callback.py # 回调处理
│ └── health.py # 健康检查
├── games/ # 游戏模块
│ ├── base.py # 游戏基类
│ └── ... # 其他游戏
├── utils/ # 工具模块
│ ├── message.py # 消息发送
│ ├── parser.py # 指令解析
│ └── rate_limit.py # 限流控制
└── data/ # 数据文件
```
## 技术栈
- FastAPIWeb框架
- SQLite数据存储
- llama-index-coreAI对话框架核心
- llama-index-llms-ollamaOllama LLM集成
- Ollama本地LLM服务
# 分析
## 现有架构
1. **指令处理流程**
- 消息通过 `/api/callback` 接收
- `CommandParser` 解析指令,只处理以 `.` 开头的命令
- 非指令消息会被忽略
- 指令分发到对应的游戏处理器
2. **状态管理**
- 游戏状态存储在 `game_states`
- 使用 `(chat_id, user_id, game_type)` 作为联合主键
- 对于群组共享状态,使用 `user_id=0`(如成语接龙)
3. **异步任务**
- 已有 `periodic_cleanup` 后台清理任务示例
- 使用 `asyncio.create_task``asyncio.sleep` 实现
## 关键技术挑战
### 1. 延迟回答机制
需要实现一个基于时间间隔的判断机制:
- 收到 `.ai` 指令时,将消息加入等待队列
- 设置一个等待窗口例如5-10秒
- 如果在等待窗口内有新消息,重新计时
- 等待窗口结束后,如果没有新消息,生成回答
- 需要在 `chat_id` 级别维护等待队列和延迟任务
### 2. 长上下文管理
- 使用 llama_index 的 `ChatMemoryBuffer` 管理对话历史
- 确保超过30轮对话历史能够被保留
- 对话历史需要按 `chat_id` 独立存储
- 对话历史中需要包含用户ID信息以便区分不同用户
### 3. Ollama配置管理
- 使用全局单一配置(服务器级别)
- 配置存储在 `data/ai_config.json` 文件中
- 配置包括:服务地址、端口、模型名称
- 通过 `.aiconfig` 指令修改配置并持久化到文件
- 配置需要有默认值localhost:11434默认模型需指定
### 4. 多用户对话识别
- 对话历史中需要记录每条消息的发送者user_id
- 生成回复时,需要识别上下文中的不同用户
- 回复格式可以考虑使用 @用户 的方式
### 5. 依赖管理
- 需要添加 llama-index-core 和相关依赖
- 需要确保与现有代码库的兼容性
- 考虑资源占用内存、CPU
## 数据结构设计
### AI对话状态数据结构
对话状态由 llama_index 的 ChatMemoryBuffer 管理,存储在内存中。
需要存储的额外信息:
```python
# 存储在 game_states 表中的 state_data
{
"user_mapping": { # 用户ID到角色名称的映射
"123456": "用户1",
"789012": "用户2",
...
},
"user_count": 2 # 当前对话中的用户数量
}
```
### 配置数据结构(存储在 data/ai_config.json
```json
{
"host": "localhost",
"port": 11434,
"model": "llama3.1"
}
```
### 数据库扩展
使用 `game_states` 表存储用户映射信息:
- `chat_id`: 会话ID
- `user_id`: 0表示群组级别
- `game_type`: "ai_chat"
- `state_data`: JSON格式的用户映射信息
注意:对话历史由 ChatMemoryBuffer 在内存中管理,不持久化到数据库。
# 提议的解决方案
## 方案概述
1. 创建一个新的游戏模块 `games/ai_chat.py`,继承 `BaseGame`
2. 使用 `game_states` 表存储用户映射信息用户ID到角色名称的映射
3. 使用全局字典维护每个 `chat_id` 的延迟任务句柄
4. 使用全局字典维护每个 `chat_id` 的 ChatEngine 实例和待处理消息队列
5. 使用 `data/ai_config.json` 存储 Ollama 全局配置
6. 使用 llama_index 的 ChatMemoryBuffer 管理对话上下文(内存中)
## 实现细节
### 1. 指令注册
`utils/parser.py` 中添加:
- `.ai`: 触发AI对话
- `.aiconfig`: 配置Ollama参数
### 2. AI对话模块 (`games/ai_chat.py`)
- `handle()`: 主处理函数,处理 `.ai``.aiconfig` 指令
- `_handle_ai()`: 处理AI对话请求
- 将消息加入等待队列
- 取消旧的延迟任务(如果存在)
- 创建新的延迟任务10秒后执行
- `_handle_config()`: 处理配置请求
- 解析配置参数host, port, model
- 更新 `data/ai_config.json` 文件
- 返回配置确认消息
- `_add_to_queue()`: 将消息加入等待队列(按 chat_id 组织)
- `_delayed_response()`: 延迟回答任务(内部异步函数)
- 等待10秒后执行
- 检查队列并生成回答
- 处理任务取消异常
- `_generate_response()`: 使用LLM生成回答
- 获取或创建 ChatEngine 实例
- 获取用户角色映射
- 将队列中的消息按用户角色格式化
- 调用 ChatEngine.chat() 生成回答
- 更新 ChatMemoryBuffer
- `_get_chat_engine()`: 获取或创建ChatEngine实例
- 检查全局字典中是否已存在
- 不存在则创建新的 ChatEngine配置 ChatMemoryBuffer
- 设置系统提示(告知多用户场景)
- `_get_user_role()`: 获取用户角色名称(创建或获取映射)
- `_load_config()`: 从 JSON 文件加载配置
- `_save_config()`: 保存配置到 JSON 文件
### 3. 延迟任务管理
- 使用全局字典 `_pending_tasks` 存储每个 `chat_id` 的延迟任务句柄
- 使用全局字典 `_message_queues` 存储每个 `chat_id` 的待处理消息队列
- 使用全局字典 `_chat_engines` 存储每个 `chat_id` 的 ChatEngine 实例
- 新消息到达时,取消旧任务(调用 task.cancel())并创建新任务
- 使用 `asyncio.create_task``asyncio.sleep(10)` 实现固定10秒延迟
- 处理 `asyncio.CancelledError` 异常,避免任务取消时的错误日志
### 4. 用户角色映射机制
- 为每个 chat_id 维护用户ID到角色名称的映射如"用户1"、"用户2"
- 映射信息存储在 `game_states` 表中chat_id, user_id=0, game_type='ai_chat'
- 首次出现的用户自动分配角色名称(按出现顺序)
- 在将消息添加到 ChatMemoryBuffer 时使用角色名称作为消息角色
- 系统提示中包含:"这是一个多用户对话场景,不同用户的发言会用不同的角色标识。你需要理解不同用户的发言内容,并根据上下文给出合适的回复。"
### 4. 依赖添加
`requirements.txt` 中添加:
```
llama-index-core>=0.10.0
llama-index-llms-ollama>=0.1.0
```
### 5. 路由注册
`routers/callback.py``handle_command()` 中添加AI对话处理分支
### 6. 帮助信息更新
`games/base.py``get_help_message()` 中添加AI对话帮助
## 时间间隔判断逻辑固定10秒窗口
1. **默认等待窗口**10秒固定
2. **收到 `.ai` 指令时**
- 提取消息内容(去除 `.ai` 前缀)
- 获取用户ID和chat_id
- 将消息用户ID + 内容)加入该 `chat_id` 的等待队列
- 如果有待处理的延迟任务(检查 `_pending_tasks[chat_id]`),取消它
- 创建新的延迟任务(`asyncio.create_task(_delayed_response(chat_id))`
- 将任务句柄存储到 `_pending_tasks[chat_id]`
3. **在等待窗口内收到新消息**(无论是否是指令):
- 如果新消息也是 `.ai` 指令:
- 将新消息加入队列
- 取消当前延迟任务(`task.cancel()`
- 创建新的延迟任务重新计时10秒
- 如果新消息不是指令但chat_id在等待队列中
- 可以考虑忽略,或也加入队列(根据需求决定)
4. **等待窗口结束(延迟任务执行)**
- 检查队列中是否有消息
- 如果有,获取该 chat_id 的 ChatEngine 和用户映射
- 将队列中的消息按用户角色格式化后添加到 ChatMemoryBuffer
- 调用 ChatEngine.chat() 生成回答
- 清空队列
-`_pending_tasks` 中移除任务句柄
## 配置文件管理data/ai_config.json
- 文件结构:
```json
{
"host": "localhost",
"port": 11434,
"model": "llama3.1"
}
```
- 首次加载时如果文件不存在,创建默认配置
- 通过 `.aiconfig` 指令修改配置时,实时保存到文件
- ChatEngine 创建时从配置文件加载配置
# 当前执行步骤:"4. 执行模式 - 代码实施完成并测试通过"
# 任务进度
## [2025-10-29_23:55:08] 执行阶段完成
- 已修改:
- requirements.txt添加 llama-index-core 和 llama-index-llms-ollama 依赖
- data/ai_config.json创建默认配置文件
- utils/parser.py添加 .ai 和 .aiconfig 指令映射和解析逻辑
- games/ai_chat.py创建完整的 AI 对话模块实现
- routers/callback.py添加 ai_chat 处理分支
- games/base.py添加 AI 对话帮助信息
- 更改:
- 实现了基于 llama_index 和 Ollama 的 AI 对话功能
- 实现了固定10秒等待窗口的延迟回答机制
- 实现了用户角色映射和长上下文管理
- 实现了配置文件的 JSON 存储和管理
- 原因:按照计划实施 AI 对话功能的所有核心组件
- 阻碍因素:无
- 状态:成功
## [2025-10-30_00:56:44] 功能优化和问题修复
- 已修改:
- games/ai_chat.py优化错误处理和用户体验
1. 移除收到消息后的确认回复(静默处理)
2. 修复转义字符警告SyntaxWarning
3. 改进错误处理,提供详细的调试信息和排查步骤
4. 添加超时设置120秒
5. 针对NPS端口转发的特殊错误提示
- 更改:
- 优化了错误提示信息,包含当前配置、测试命令和详细排查步骤
- 专门针对NPS端口转发场景添加了Ollama监听地址配置说明
- 改进了连接错误的诊断能力
- 原因:根据实际使用中发现的问题进行优化
- 阻碍因素:无
- 状态:成功
## [2025-10-30_01:10:05] 系统提示词持久化和功能完善
- 已修改:
- games/ai_chat.py
1. 实现系统提示词的持久化存储(保存到配置文件)
2. 添加 `_get_default_system_prompt()` 方法定义默认系统提示词
3. 添加 `_get_system_prompt()` 方法从配置文件加载系统提示词
4. 更新系统提示词内容明确AI身份和职责
5. 在系统提示词中包含完整的机器人功能列表和指引
- 更改:
- 系统提示词现在会保存到 `data/ai_config.json` 文件中
- 服务重启后系统提示词会自动从配置文件加载,保持长期记忆
- AI助手能够了解自己的身份和所有机器人功能可以主动指引用户
- 系统提示词包含了完整的13个功能模块介绍和回复指南
- 原因实现系统提示词的长期记忆让AI能够始终记住自己的身份和职责
- 阻碍因素:无
- 状态:成功
# 最终审查
## 实施总结
✅ 所有计划功能已成功实施并通过测试
### 核心功能实现
1. ✅ AI对话系统基于 llama_index + Ollama 构建
2. ✅ 显式指令触发(`.ai <问题>`
3. ✅ 配置指令(`.aiconfig`支持动态配置Ollama服务
4. ✅ 固定10秒等待窗口的延迟回答机制
5. ✅ 用户角色映射和长上下文管理30+轮对话)
6. ✅ 配置文件持久化存储
7. ✅ 系统提示词持久化存储(新增)
8. ✅ 完善的错误处理和调试信息
### 文件修改清单
- ✅ requirements.txt - 添加依赖
- ✅ data/ai_config.json - 配置文件(包含系统提示词)
- ✅ utils/parser.py - 指令解析
- ✅ games/ai_chat.py - AI对话模块完整实现
- ✅ routers/callback.py - 路由注册
- ✅ games/base.py - 帮助信息更新
### 技术特性
- ✅ 多用户对话支持
- ✅ 延迟任务管理asyncio
- ✅ ChatMemoryBuffer长上下文管理
- ✅ JSON配置文件管理
- ✅ NPS端口转发支持
- ✅ 详细的错误诊断和排查指南
### 测试状态
- ✅ 功能测试通过
- ✅ Ollama服务连接测试通过
- ✅ NPS端口转发配置测试通过
- ✅ 系统提示词持久化测试通过
## 实施与计划匹配度
实施与计划完全匹配 ✅
## 补充分析Markdown 渲染与发送通道2025-10-30
### 现状观察
- `routers/callback.py` 中仅当 `response_text.startswith('#')` 时才通过 `send_markdown()` 发送,否则使用 `send_text()`。这意味着即使 AI 返回了合法的 Markdown但不以 `#` 开头例如代码块、列表、表格、普通段落等也会被按纯文本通道发送导致下游WPS 侧)不进行 Markdown 渲染。
- `games/ai_chat.py` 的 `_generate_response()` 直接返回 `str(response)`,未对内容类型进行标注或判定,上层仅依赖首字符为 `#` 的启发式判断来选择发送通道。
- `utils/message.py` 已具备 `send_markdown()` 与 `send_text()` 两种发送方式,对应 `{"msgtype":"markdown"}` 与 `{"msgtype":"text"}` 消息结构;当前缺少自动识别 Markdown 的逻辑。
### 影响
- 当 AI 返回包含 Markdown 元素但非标题(不以 `#` 开头)的内容时,用户端看到的是未渲染的原始 Markdown 文本,表现为“格式不能成功排版”。
### 待确认问题(不含解决方案,需产品/实现口径)
1. 目标平台WPS 机器人)对 Markdown 的要求是否仅需 `msgtype=markdown` 即可渲染?是否存在必须以标题开头的限制?
2. 期望策略:
- 是否希望“.ai 的所有回复”统一走 Markdown 通道?
- 还是需要基于 Markdown 特征进行判定(如代码块、列表、链接、表格、行内格式等)?
3. 兼容性:若统一改为 Markdown 通道,是否会影响既有纯文本展示(例如换行、转义、表情)?
4. 其他指令模块是否也可能返回 Markdown若有是否一并纳入同一策略
### 相关代码参照点(路径)
- `routers/callback.py`:回复通道选择逻辑(基于 `startswith('#')`
- `games/ai_chat.py`AI 回复内容生成与返回(直接返回字符串)
- `utils/message.py``send_markdown()` 与 `send_text()` 的消息结构
### 决策结论与范围2025-10-30
- 分支策略:不创建新分支,继续在当前任务上下文内推进。
- 发送策略:`.ai` 产生的回复统一按 Markdown 发送。
- 影响范围:仅限 AI 对话功能(`.ai`/`ai_chat`),不扩展到其他指令模块。
# 任务进度(补充)
## [2025-10-30_??:??:??] 标注 Markdown 渲染问题(记录现状与待确认项)
- 已修改:
- `.tasks/2025-10-29_3_ai_chat.md`补充“Markdown 渲染与发送通道”分析与待确认清单(仅问题陈述,无解决方案)。
- 更改:
- 明确当前仅以标题开头触发 Markdown 发送的启发式导致部分 Markdown 未被渲染。
- 原因:
- 用户反馈“AI 返回内容支持 Markdown但当前直接当作文本返回导致无法正确排版”。
- 阻碍因素:
- 目标平台的 Markdown 渲染细节与统一策略选择待确认。
- 状态:
- 未确认(等待策略口径与平台渲染规范确认)。
## [2025-10-30_11:40:31] 执行AI 回复统一按 Markdown 发送(仅限 AI
- 已修改:
- `routers/callback.py`:在 `callback_receive()` 的发送阶段,当 `game_type == 'ai_chat'` 且存在 `response_text` 时,无条件调用 `send_markdown(response_text)`;若发送异常,记录日志并回退到 `send_text(response_text)`;其他指令模块继续沿用 `startswith('#')` 的启发式逻辑。
- 更改:
- 使 `.ai` 产生的回复在 WPS 端稳定触发 Markdown 渲染,不再依赖以 `#` 开头。
- 原因:
- 对齐“统一按 Markdown 发送(仅限 AI”的决策解决 Markdown 文本被当作纯文本发送导致的排版问题。
- 阻碍因素:
- 暂无。
- 状态:
- 成功。