blob: 2c7793531939603b25819e92e211d461048b08cb [file] [log] [blame] [view] [raw]
# Telegram Bot 上下文管理重构技术设计文档
---
## 文档信息
| 项目 | 内容 |
|------|------|
| **文档标题** | Telegram Bot 上下文管理重构技术设计 |
| **版本** | V1.0 |
| **作者** | Claude Agent |
| **创建日期** | 2025-12-10 |
| **状态** | 待评审 |
---
## 变更记录
| 版本 | 日期 | 作者 | 变更内容 |
|------|------|------|----------|
| V1.0 | 2025-12-10 | Claude Agent | 初始版本 |
---
## 1. 背景与问题
### 1.1 问题描述
Telegram Bot 在长时间对话后出现"抽风"现象:
- 新发送的消息不被响应
- Bot 回复内容与上下文无关或完全混乱
- API 请求超时或失败
### 1.2 根本原因分析
通过代码审查发现,问题根源是**上下文管理机制的设计缺陷**:
#### 问题1: 提示词爆炸式增长
**位置**: `src/claude_agent/telegram/claude_adapter.py` `_build_full_prompt()` 方法
```
每次请求都将 CLAUDE.md + 全部历史消息 + 新消息 拼接成一个巨大字符串
没有任何 Token 限制检查
```
#### 问题2: 历史记录污染
**位置**: `src/claude_agent/core/agent.py` `process_user_input()` 方法
```
完整的 full_prompt (包含系统提示和所有历史) 被原样存入 conversation_history
导致历史记录指数级膨胀:
- 第1条: CLAUDE.md + 消息1
- 第2条: CLAUDE.md + (CLAUDE.md + 消息1) + 消息2
- 第N条: 指数级增长
```
#### 问题3: 粗暴的截断机制
**位置**: `src/claude_agent/telegram/claude_adapter.py` 758-773
```python
if len(full_prompt) > 1000000: # 1MB 才触发!
full_prompt = full_prompt[:20000] # 字符截断,破坏上下文结构
```
- 1MB 阈值过高(Claude 200K token 限制早已超出)
- 字符截断破坏 JSON、代码块、对话结构
### 1.3 Claude Code SDK 特性
经研究确认,Claude Code SDK `query()` 函数是**无状态的**:
- 每次调用独立,不维护会话上下文
- 需要应用层自行管理对话历史
- 这是正确的设计,问题在于项目的实现方式
---
## 2. 设计目标
### 2.1 核心目标
1. **修复上下文膨胀问题** - 防止历史记录指数级增长
2. **实现智能截断** - 基于 Token 而非字符进行上下文管理
3. **保持功能完整** - 持久化、多平台支持不受影响
### 2.2 性能指标
| 指标 | 目标值 |
|------|--------|
| 最大支持对话轮数 | 50+ 轮(当前配置) |
| 单条消息响应时间 | < 5 |
| 上下文 Token 利用率 | > 80% |
| 内存占用(每会话) | < 1MB |
### 2.3 约束条件
- 保持与 SSHOUTCLI 等其他平台的兼容性
- 保持持久化存储功能(重启后恢复对话)
- 不改变现有配置文件格式
---
## 3. 架构设计
### 3.1 当前架构问题
```mermaid
flowchart TD
subgraph 当前流程-有问题
A[用户消息] --> B[_build_full_prompt]
B --> C["拼接: CLAUDE.md + 全部历史 + 消息"]
C --> D[传递给 AgentCore]
D --> E["存入 conversation_history<br/>(包含完整拼接内容)"]
E --> F[query SDK]
F --> G[响应也存入历史]
G --> H["下次请求时历史更大<br/>恶性循环"]
end
style C fill:#ff6b6b
style E fill:#ff6b6b
style H fill:#ff6b6b
```
### 3.2 目标架构
```mermaid
flowchart TD
subgraph 新流程-修复后
A[用户消息] --> B[ContextBuilder]
B --> C["分离: 系统提示 / 历史 / 当前消息"]
C --> D[TokenCounter]
D --> E{超出限制?}
E -->|是| F[智能截断-保留最近]
E -->|否| G[构建最终 Prompt]
F --> G
G --> H[query SDK]
H --> I["仅存储原始消息<br/>(不含系统提示)"]
I --> J[持久化存储]
end
style C fill:#51cf66
style F fill:#51cf66
style I fill:#51cf66
```
### 3.3 组件职责重新划分
```mermaid
classDiagram
class ContextBuilder {
<<新增>>
+system_prompt: str
+max_tokens: int
+build_prompt(history, message): str
+estimate_tokens(text): int
-_truncate_history(history, budget): List
}
class MessageStore {
<<重构>>
+add_message(role, content)
+get_recent(limit): List
+get_token_count(): int
-_messages: deque
-_max_messages: int
}
class ClaudeAgentAdapter {
<<修改>>
-context_builder: ContextBuilder
-message_store: Dict[str, MessageStore]
+create_streaming_response(chat_id, message)
-_save_raw_message(chat_id, role, content)
}
class AgentCore {
<<修改>>
+conversation_history: List
+add_message(role, content)
+get_context_for_prompt(): List
-_ensure_history_clean()
}
ContextBuilder --> MessageStore : uses
ClaudeAgentAdapter --> ContextBuilder : uses
ClaudeAgentAdapter --> AgentCore : manages
```
---
## 4. 详细设计
### 4.1 Token 计数器
**目的**: 准确估算文本的 Token 数量,用于上下文管理
**实现方案**: 使用简化的中英文混合估算(避免引入 tiktoken 依赖)
```mermaid
flowchart LR
A[输入文本] --> B{语言检测}
B -->|英文为主| C["字符数 / 4"]
B -->|中文为主| D["字符数 / 1.5"]
B -->|混合| E["加权计算"]
C --> F[Token 估算值]
D --> F
E --> F
```
**设计接口**:
```mermaid
classDiagram
class TokenEstimator {
<<utility>>
+estimate(text: str): int
+estimate_messages(messages: List): int
+CHARS_PER_TOKEN_EN: float = 4.0
+CHARS_PER_TOKEN_ZH: float = 1.5
+MAX_CONTEXT_TOKENS: int = 150000
}
```
### 4.2 上下文构建器
**目的**: 分离系统提示、历史消息、当前消息的组装逻辑
**核心原则**:
1. **系统提示只设置一次**,不在每次请求时重复包含
2. **历史消息只存储原始内容**,不存储拼接后的 prompt
3. **智能截断从最老的消息开始**,保留最近的上下文
```mermaid
sequenceDiagram
participant User as 用户消息
participant CB as ContextBuilder
participant TE as TokenEstimator
participant Store as MessageStore
participant SDK as Claude SDK
User->>CB: build_prompt(message)
CB->>Store: get_recent_messages()
Store-->>CB: [msg1, msg2, ..., msgN]
CB->>TE: estimate_tokens(system + history + message)
TE-->>CB: total_tokens
alt tokens > MAX_LIMIT
CB->>CB: truncate_history(keep_recent)
CB->>TE: re-estimate
end
CB->>SDK: query(final_prompt)
SDK-->>CB: response
CB->>Store: add_message("user", original_message)
CB->>Store: add_message("assistant", response)
```
### 4.3 消息存储重构
**当前问题**: `conversation_history` 存储了拼接后的完整 prompt
**修复方案**: 只存储原始消息内容
```mermaid
flowchart TD
subgraph 消息存储结构
A[MessageStore]
A --> B["messages: deque(maxlen=100)"]
B --> C["{'role': 'user', 'content': '原始消息', 'timestamp': 123}"]
B --> D["{'role': 'assistant', 'content': 'AI回复', 'timestamp': 124}"]
end
subgraph 禁止存储
E["❌ 系统提示"]
F["❌ 拼接后的历史"]
G["❌ full_prompt"]
end
```
### 4.4 配置扩展
`configs/default.toml` 中新增上下文管理配置:
```toml
# 上下文管理配置
[context]
# 最大上下文 Token 数 (预留 50K 给响应)
max_context_tokens = 150000
# 系统提示最大 Token 数
max_system_prompt_tokens = 10000
# 每条消息最大 Token 数 (超出则截断)
max_message_tokens = 5000
# 历史消息最小保留数量 (即使超出 token 也保留)
min_history_messages = 5
# Token 估算方式: "simple" | "tiktoken"
token_estimation = "simple"
```
---
## 5. 核心代码修改点
### 5.1 修改清单
| 文件 | 修改类型 | 修改内容 |
|------|----------|----------|
| `src/claude_agent/utils/token_estimator.py` | **新增** | Token 估算工具类 |
| `src/claude_agent/core/context_builder.py` | **新增** | 上下文构建器 |
| `src/claude_agent/core/agent.py` | **修改** | 移除污染的历史存储逻辑 |
| `src/claude_agent/telegram/claude_adapter.py` | **修改** | 重构 `_build_full_prompt`,使用新组件 |
| `configs/default.toml` | **修改** | 新增 `[context]` 配置节 |
### 5.2 关键修改详解
#### 5.2.1 `agent.py` 修改
**移除**: 83行的污染存储
```python
# 当前代码 (有问题)
async def process_user_input(self, user_input: str) -> str:
self.conversation_history.append({"role": "user", "content": user_input}) # ❌ user_input 是完整 prompt
# 修改后
async def process_user_input(self, user_input: str) -> str:
# 不在这里存储,由 adapter 层控制
pass
```
**新增**: 清洁的消息添加方法
```python
def add_raw_message(self, role: str, content: str, max_length: int = 5000):
"""添加原始消息(自动截断过长内容)"""
if len(content) > max_length:
content = content[:max_length] + "...[截断]"
self.conversation_history.append({
"role": role,
"content": content,
"timestamp": int(time.time())
})
self._trim_if_needed()
```
#### 5.2.2 `claude_adapter.py` 修改
**重构**: `_build_full_prompt` 方法
```python
# 当前代码 (有问题)
def _build_full_prompt(self, user_context, message, conversation_history):
prompt_parts = []
prompt_parts.append(f"系统提示:\n{self._claude_md_content}") # ❌ 每次都加
prompt_parts.append(f"群聊历史对话:\n...") # ❌ 包含污染的历史
return "\n\n".join(prompt_parts)
# 修改后
def _build_full_prompt(self, user_context: str, message: str,
conversation_history: List[Dict]) -> str:
"""构建提示词(使用 ContextBuilder)"""
return self._context_builder.build(
system_prompt=self._claude_md_content,
history=conversation_history,
user_context=user_context,
current_message=message
)
```
**新增**: 响应后只存储原始消息
```python
async def _handle_response(self, chat_id: str, user_message: str, response: str):
"""处理响应后的存储"""
agent = self._agents.get(chat_id)
if agent:
# 只存储原始消息,不存储 full_prompt
agent.add_raw_message("user", user_message)
agent.add_raw_message("assistant", response)
await self._save_agent_state(chat_id, agent)
```
---
## 6. 数据流图
### 6.1 消息处理完整流程
```mermaid
sequenceDiagram
participant TG as Telegram
participant Bot as TelegramBot
participant Adapter as ClaudeAgentAdapter
participant CB as ContextBuilder
participant Agent as AgentCore
participant SDK as Claude SDK
participant Store as PersistenceManager
TG->>Bot: 用户消息
Bot->>Adapter: handle_message(chat_id, text)
Adapter->>Adapter: _get_or_create_agent(chat_id)
Adapter->>Agent: get_conversation_history()
Agent-->>Adapter: [原始历史消息]
Adapter->>CB: build(system, history, message)
CB->>CB: estimate_tokens()
CB->>CB: truncate_if_needed()
CB-->>Adapter: final_prompt
Adapter->>SDK: query(final_prompt)
SDK-->>Adapter: response_stream
loop 流式响应
Adapter->>TG: 更新消息
end
Adapter->>Agent: add_raw_message("user", original_text)
Adapter->>Agent: add_raw_message("assistant", response)
Adapter->>Store: save_agent_state()
```
### 6.2 Token 预算分配
```mermaid
pie title Token 预算分配 (200K 总限制)
"系统提示 (CLAUDE.md)" : 10
"用户上下文" : 2
"历史消息" : 60
"当前消息" : 8
"预留给响应" : 20
```
---
## 7. 风险评估
| 风险 | 可能性 | 影响 | 缓解措施 |
|------|--------|------|----------|
| Token 估算不准确 | | | 预留 20% 安全边际;支持切换到 tiktoken |
| 历史截断丢失重要上下文 | | | 保证最少 5 条历史;优先截断最老消息 |
| 持久化数据格式不兼容 | | | 添加版本号;编写迁移脚本 |
| 多平台行为不一致 | | | 统一使用 ContextBuilder |
---
## 8. 测试计划
### 8.1 单元测试
| 测试项 | 测试内容 | 预期结果 |
|--------|----------|----------|
| TokenEstimator.estimate | 中英文混合文本 | 误差 < 20% |
| ContextBuilder.build | 正常上下文构建 | 包含所有必要部分 |
| ContextBuilder.truncate | 超长历史截断 | 保留最近消息 |
| Agent.add_raw_message | 消息存储 | 只存储原始内容 |
| Agent.add_raw_message | 超长消息 | 自动截断 |
### 8.2 集成测试
| 测试场景 | 步骤 | 预期结果 |
|----------|------|----------|
| 长对话测试 | 连续发送 100+ 条消息 | Bot 持续正常响应 |
| 大消息测试 | 发送 10KB+ 的长消息 | 消息被截断,Bot 正常响应 |
| 重启恢复测试 | 对话后重启 Bot | 历史正确恢复,无污染 |
| 并发测试 | 多个群组同时对话 | 上下文不串台 |
### 8.3 黑盒测试
```bash
# 1. 长对话压力测试
python tests/blackbox/test_long_conversation.py
# 2. 上下文正确性验证
python tests/blackbox/test_context_integrity.py
# 3. 持久化恢复测试
python tests/blackbox/test_persistence_recovery.py
```
---
## 9. 快速参考表 (For Code Review)
| 核心组件/配置 | 实现状态 | 文件路径 | 核心功能 | 关键代码位置 |
|--------------|----------|----------|----------|--------------|
| **新增组件** | | | | |
| `TokenEstimator` | 待实现 | `src/claude_agent/utils/token_estimator.py` | Token 估算 | `estimate()` 方法 |
| `ContextBuilder` | 待实现 | `src/claude_agent/core/context_builder.py` | 上下文构建 | `build()` 方法 |
| **修改组件** | | | | |
| `AgentCore` | 待修改 | `src/claude_agent/core/agent.py` | 移除污染存储 | `add_raw_message()` 方法 |
| `ClaudeAgentAdapter` | 待修改 | `src/claude_agent/telegram/claude_adapter.py` | 重构 prompt 构建 | `_build_full_prompt()` 方法 |
| **配置文件** | | | | |
| `default.toml` | 待修改 | `configs/default.toml` | 新增 context 配置 | `[context]` |
| **待删除代码** | | | | |
| 历史污染存储 | 待删除 | `src/claude_agent/core/agent.py` | 83 append 逻辑 | N/A |
| 粗暴截断逻辑 | 待删除 | `src/claude_agent/telegram/claude_adapter.py` | 758-773 | N/A |
---
## 10. 实施计划
### Phase 1: 基础组件 (无破坏性修改)
1. 新增 `TokenEstimator`
2. 新增 `ContextBuilder`
3. 编写单元测试
### Phase 2: 核心重构
1. 修改 `AgentCore.add_raw_message()`
2. 重构 `ClaudeAgentAdapter._build_full_prompt()`
3. 更新配置文件
### Phase 3: 清理与测试
1. 移除旧的污染代码
2. 运行完整测试套件
3. 进行黑盒测试验证
### Phase 4: 数据迁移
1. 编写历史数据清理脚本
2. 清理现有被污染的 `conversations.json`
---
## 11. Review Checklist
- [ ] Token 估算逻辑是否准确(误差 < 20%)
- [ ] 上下文构建是否正确分离系统提示和历史
- [ ] 消息存储是否只存储原始内容
- [ ] 截断逻辑是否保留最近消息
- [ ] 持久化数据格式是否向后兼容
- [ ] 配置项是否合理
- [ ] 单元测试覆盖率是否达到 80%
- [ ] 是否有潜在的内存泄漏
- [ ] 多平台(Telegram/SSHOUT/CLI)行为是否一致
- [ ] 错误处理是否完善
---
## 附录 A: 参考资料
- Claude API Token 限制: https://docs.anthropic.com/claude/docs/models-overview
- 项目 CLAUDE.md: `/home/bmy001/Work/aichatbot/CLAUDE.md`
- 现有架构文档: `/home/bmy001/Work/aichatbot/docs/technical/DIRECTORY_STRUCTURE.md`