243 lines
9.3 KiB
Markdown
Raw Permalink 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.

# PicoBot 架构机制
## 核心数据流
```
Channel → MessageBus → SessionManager → AgentLoop → (tools) → SessionManager → MessageBus → OutboundDispatcher → Channel
ControlChannel → SessionManager (dialog 操作: 创建/切换/归档/删除)
```
## 模块职责
| 模块 | 职责 |
|------|------|
| `gateway` | HTTP/WebSocket 服务器,持有 GatewayState |
| `client` | TUI 聊天客户端 |
| `channels` | 外部集成飞书、CLI仅收发消息 |
| `bus` | 异步消息队列,纯队列不路由 |
| `session` | 会话生命周期管理、dialog 操作 |
| `agent` | LLM 调用循环、工具执行、上下文压缩、媒体处理、子 Agent |
| `providers` | LLM API 客户端OpenAI 兼容、Anthropic |
| `tools` | Agent 工具bash、文件操作、搜索、HTTP、web、browser、memory、delegate 等) |
| `skills` | Skill 加载、管理和 prompt 构建 |
| `storage` | SQLite 持久化 |
| `scheduler` | Cron 作业调度 |
| `observability` | Observer 模式agent/工具遥测事件 |
| `protocol` | WebSocket 协议消息定义 |
| `config` | 配置加载、环境变量替换、路径解析 |
| `memory` | 长期记忆存储与检索 |
| `mcp` | MCPModel Context Protocol工具集成 |
## 功能边界
- Channels 仅收发消息,不感知 session 或 LLM
- MessageBus 是纯异步队列,不路由
- SessionManager 拥有 session 状态,不直接调 LLM负责注入 skills prompt
- AgentLoop 无状态,接收 dialog 事件调用 LLM、执行工具
- Providers 是纯 HTTP 客户端,无 bus/session/channel 感知
- Tools 接收原始参数,返回字符串结果
- MCP 工具在 Gateway 初始化时连接服务器、发现工具,并包装成普通 Tool 注册到 ToolRegistry
- 子 Agent 由 `delegate` 工具创建,复用 provider 配置和按需过滤后的工具集;后台任务结果通过 MessageBus 发回原会话
## 关键约束
- Gateway 启动时切换到 workspace 目录
- SQLite 数据在 `{workspace}/picobot.db`
- ChannelManager 持有 MessageBus 和所有 channel
- OutboundDispatcher 通过 ChannelManager 路由出站消息
- Config `.env` 加载使用 `unsafe { env::set_var(...) }`
- `browser` 工具只有在 `browser.enabled=true` 时注册,依赖 Chrome/Chromium 与 WebDriver
## 上下文压缩
当上下文接近 token 限制时触发:
1. **快速裁剪**:合并连续同角色消息,截断工具输出
2. **硬截断**:移除过老消息
3. 压缩后保留用户消息确保结构完整
## Skill 系统
三个优先级(高覆盖低):
1. `{workspace}/skills/` — 最高优先级
2. `~/.picobot/skills/` — 中等优先级
3. `~/.agents/skills/` — 最低优先级
同名 skill 按优先级覆盖。每个 skill 是包含 `SKILL.md` 的目录。内置 skill 在 `~/.picobot/skills/` 下不存在时自动从二进制释放安装。
## 会话系统
### 会话 ID 格式
统一会话 ID 为三段式:**`<channel>:<chat_id>:<dialog_id>`**
| 部分 | 含义 | 示例 |
|------|------|------|
| `channel` | 消息渠道 | `cli_chat``feishu` |
| `chat_id` | 聊天/群组标识 | `sid_abc123` |
| `dialog_id` | 对话标识 | `default``d_xxxx`(短 ID |
同一 `channel:chat_id` 下可有多个 dialog。`chat_scope()` 返回 `"channel:chat_id"` 用于分组。
### Session 生命周期
```
create → 存入 Storage → 载入 memory → 设为当前 dialog
get_or_create
← 接收消息、LLM 响应 →
switch → rename → archive → delete(soft)
```
| 操作 | 效果 |
|------|------|
| `create` | 新建 dialog_id立即持久化到 SQLite设为当前 |
| `get_or_create` | 先在内存 HashMap 中找 → 再查 Storage → 都不存在则新建 |
| `switch_dialog` | 切换当前 dialog目标 session 自动从 Storage 恢复入内存 |
| `list_dialogs` | 列出 `channel:chat_id` 下最近 10 个 session |
| `rename` | 更新标题,内存 + Storage 同步 |
| `delete` | 软删除(设 deleted_at从内存移除 |
| `archive` | 当前为空操作 |
### SessionManager 数据结构
两层追踪:
- **`sessions`**`HashMap<String, Arc<Mutex<Session>>>` — 所有已加载的 sessionkey 为完整 session ID
- **`current_sessions`**`HashMap<String, String>` — 每个 `channel:chat_id` 当前的 session ID
消息到达时 `resolve_dialog_id()` 按顺序确定接收 session当前 session → Storage 最近活跃 session → 新建。
### 消息处理三阶段
**阶段 1持锁**:斜杠命令检测 → 用户消息入库 → 提取记忆上下文 → 构建系统提示skills + memory_context→ 上下文压缩 → 创建 AgentLoop
**阶段 2无锁**`agent.process(history)` → LLM 调用 + 工具执行。上下文溢出时自动重新压缩重试
**阶段 3持锁**:持久化 agent 响应消息 → 自动生成标题(消息数 ≥ 5 且标题为"新对话"时)
### 会话恢复
从 Storage 恢复 session 时:
-`last_compressed_message_at` 存在:先加载近 3 条 Timeline 记忆作为 `[Previous Context]`,再加载压缩标记后的原始消息
- 若无压缩记录:正常加载全部消息
- 自动修复断链的工具调用gateway 崩溃中途重启导致)
---
## 记忆系统
### 记忆类别
| 类别 | 用途 | 生命周期 | 检索方式 |
|------|------|----------|----------|
| **Knowledge** | 事实、偏好、模式、洞察 | 长期保留,手动删除 | 每轮注入系统提示,关键词匹配 |
| **Timeline** | 历史会话摘要 | 自动清理(默认 90 天) | `timeline_recall` 工具按需检索 |
### MemoryEntry 字段
| 字段 | 说明 |
|------|------|
| `id` | UUID |
| `key` | 唯一键,同 key 写入覆盖旧值 |
| `content` | 记忆内容 |
| `category` | `knowledge``timeline` |
| `importance` | 权重 (0.01.0)Timeline 默认为 0.3 |
| `session_id` | 关联会话(可选) |
### 存储与检索
- 主表 `memories` + FTS5 虚拟表 `memory_fts(key, content)` 全文索引
- 中文分词使用 jieba-rs 逐词精确匹配,用 OR 连接
- FTS5 无结果时回退到 LIKE 模糊匹配
- 支持 category、session_id、时间范围过滤
### 工作流程
```
用户消息到达
→ MemoryManager::recall(content, 5, Knowledge)
返回最多 5 条匹配的知识记忆(按 importance DESC
→ 格式化为 "- key: content"
→ 注入系统提示的 "记忆上下文" 部分
→ LLM 可见,辅助回答
```
### 记忆工具
| 工具 | 写操作 | 说明 |
|------|--------|------|
| `memory_store` | 是 | 存储 Knowledge。必填: key, content。可选: importance |
| `memory_recall` | 否 | 搜索 Knowledge。必填: query(空格分隔关键词)。可选: since, until, limit |
| `timeline_recall` | 否 | 搜索 Timeline压缩后的会话摘要。必填: query。可选: session_id, since, until |
| `memory_forget` | 是 | 按 key 删除记忆 |
### 上下文压缩与 Timeline
LLM 对话上下文接近 token 限制 (默认 128K × 70%) 时自动触发压缩:
1. **快速裁剪**:工具输出 ≥ 2000 字符时截断
2. **LLM 摘要**:最多 3 轮,每轮找连续用户消息对,将中间的 assistant/tool 消息压缩为摘要 → 摘要作为 **Timeline 记忆** 持久化importance 0.3
3. **硬截断**:若仍超 90%,只保留前 N + 后 N 条消息
压缩后 `last_compressed_message_at` 标记边界,后续恢复时从标记点加载原始消息,以 Timeline 提供更早的上下文。
### 关键集成点
| 时机 | 操作 |
|------|------|
| 每次消息处理 | `memory_manager.recall()` 提取 Knowledge 上下文 |
| 系统提示构建 | `MemorySection` 渲染记忆指南 + 匹配的记忆 |
| 有压缩历史时 | `HistorySection` 提示 LLM 使用 `timeline_recall` |
| 压缩完成后 | 摘要自动存储为 Timeline 记忆 |
| 空闲时 | 可配置自动 consolidation`idle_consolidation_minutes` |
---
## MCP 工具集成
Gateway 初始化时读取 `config.mcp.servers`
1. 按服务器配置连接 `stdio``sse``streamable-http` 传输
2. 调用 MCP `list_tools`
3. 将每个 MCP tool 包装为 `McpToolWrapper`
4. 注册到当前 session 的 `ToolRegistry`
`/mcp` 斜杠命令会显示 MCP 服务器连接状态和工具列表。
---
## 子 Agent / delegate
`delegate` 工具用于把独立任务交给子 Agent
| 模式 | 行为 |
|------|------|
| `inline` | 当前轮阻塞等待子 Agent 返回 |
| `background` | 后台运行,完成后通过原 channel/chat 通知 |
| `parallel` | 多个子 Agent 并发执行并聚合结果 |
默认工具集是只读工具:`file_read``file_search``content_search``web_fetch``http_request``calculator`。调用时可通过 `allowed_tools` 显式放开其他工具。后台任务会写入 `background_tasks` 表,默认 24 小时后清理。
---
## 当前斜杠命令
| 命令 | 说明 |
|------|------|
| `/new` | 创建新对话 |
| `/sessions` | 列出最近对话 |
| `/switch <dialog_id>` | 切换到指定对话 |
| `/rename <title>` | 重命名当前对话 |
| `/delete` | 删除当前对话 |
| `/compact` | 手动触发上下文压缩 |
| `/info` | 显示当前对话信息 |
| `/dump` | 保存当前对话为 markdown |
| `/?`, `/help` | 显示帮助 |
| `/mcp` | 显示 MCP 状态 |
| `/stop` | 停止当前任务并清空消息队列 |