132089c8eb
用户发送 /clear 时主动截断上下文:加载历史 → LLM 生成摘要归档 → 丢弃在途会话 → 回复确认(带摘要预览)。 - context/types: SummaryReason 新增 Clear 变体 - context/builder: 抽出 archive_session 共享逻辑, 新增 trigger_clear_summary(返回摘要文本) - db/models: 新增 latest_session_boundary;list_recent_chat_records _for_context 按会话边界过滤(clear/timeout),并排除 source=clear - daemon: SessionMap 记录 user_id;拦截 /clear 走 handle_clear_command; 新增 build_clear_reply 纯函数 - migration: session_summaries (user_id, created_at DESC) 索引 会话边界机制统一 clear 与 12h 空闲超时;摘要不注入下一对话, 只存档供 read_summaries 查询。新增 4 个单元测试。
472 lines
14 KiB
Markdown
472 lines
14 KiB
Markdown
# iAs 代码调用图谱
|
||
|
||
> 微信 AI 智能助手 —— 完整架构与调用关系
|
||
|
||
---
|
||
|
||
## 1. 全局架构总览
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph CLI["CLI 入口 (main.rs)"]
|
||
A1["clap::Parser Cli 解析"]
|
||
A2["cmd_login"]
|
||
A3["cmd_listen"]
|
||
A4["cmd_send"]
|
||
A5["cmd_whoami"]
|
||
A6["cmd_usage"]
|
||
A7["cmd_tool"]
|
||
A8["cmd_daemon"]
|
||
end
|
||
subgraph DAEMON["守护进程 (daemon.rs)"]
|
||
B0["run()"]
|
||
B1["spawn_consumers()"]
|
||
B2["llm_consumer_loop"]
|
||
B3["tool_consumer_loop"]
|
||
B4["send_consumer_loop"]
|
||
B5["run_llm_round / resume_llm_round"]
|
||
B6["handle_chat_result"]
|
||
B7["execute_tool"]
|
||
end
|
||
subgraph QUEUE["消息队列 (queue/)"]
|
||
C1["MessageQueue 公平轮转"]
|
||
C2["QueueRunner 路由调度"]
|
||
C3["EnqueueHandle"]
|
||
C4["ConsumerChannels"]
|
||
end
|
||
subgraph LLM["LLM 对话系统 (llm/)"]
|
||
D1["Conversation 对话管理器"]
|
||
D2["chat_with_tools() / run_tool_loop()"]
|
||
D3["DeepSeekProvider"]
|
||
D4["LlmProvider trait"]
|
||
D5["StreamReceiver"]
|
||
end
|
||
subgraph CONTEXT["上下文管理 (context/)"]
|
||
E1["ChatSession 消息历史+摘要"]
|
||
E2["builder Token估算+上下文构建"]
|
||
E3["MemoryStore 长期记忆"]
|
||
E4["read_summaries"]
|
||
end
|
||
subgraph TOOLS["工具系统 (tools/)"]
|
||
F1["BuiltinRegistry 工具注册表"]
|
||
F2["ApprovalManager 审批管理"]
|
||
F3["build_capability_guide"]
|
||
F4["unpack_call_params"]
|
||
subgraph BUILTINS["内置工具 (builtins/)"]
|
||
G1["datetime"]
|
||
G2["weather"]
|
||
G3["web_search"]
|
||
G4["fetch_page"]
|
||
G5["memos"]
|
||
G6["amap"]
|
||
end
|
||
end
|
||
subgraph WECHAT["微信通道 (wechat/)"]
|
||
H1["WeChatClient"]
|
||
H2["receive_messages()"]
|
||
H3["send_text()"]
|
||
H4["login()"]
|
||
H5["notify_start/stop"]
|
||
end
|
||
subgraph DB["数据库 (db/)"]
|
||
I1["Database::connect()"]
|
||
I2["models CRUD 操作"]
|
||
end
|
||
subgraph OTHER["其他模块"]
|
||
J1["state.rs 文件状态回退"]
|
||
J2["scheduler.rs 定时任务"]
|
||
J3["logger.rs 日志初始化"]
|
||
J4["channel/mod.rs ChannelId"]
|
||
J5["ipc.rs UDS 协议(已废弃)"]
|
||
J6["worker.rs 旧架构(已废弃)"]
|
||
end
|
||
|
||
A1 --> A2 & A3 & A4 & A5 & A6 & A7 & A8
|
||
A3 --> B0
|
||
A8 --> B0
|
||
B0 --> B1
|
||
B1 --> B2 & B3 & B4
|
||
B2 --> B5 & B6
|
||
B3 --> B7
|
||
B0 --> C1 & C2 & C3
|
||
C2 --> C4
|
||
C4 --> B2 & B3 & B4
|
||
B2 --> D1
|
||
D1 --> D2
|
||
D2 --> D3
|
||
D3 --> D4
|
||
D4 --> D5
|
||
D1 --> E1
|
||
D2 --> E2
|
||
B2 --> E3 & E4
|
||
D2 --> F1
|
||
B3 --> F1 & F2
|
||
B7 --> F1 & F3 & F4
|
||
F1 --> G1 & G2 & G3 & G4 & G5 & G6
|
||
B4 --> H1
|
||
H1 --> H2 & H3
|
||
B0 --> I1
|
||
B2 --> I2
|
||
B4 --> I2
|
||
A2 --> I2 & J1
|
||
A3 --> J1
|
||
B0 --> J1 & J2
|
||
A1 --> J3
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 消息处理全链路
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant WX as 微信用户
|
||
participant WC as WeChatClient
|
||
participant MQ as MessageQueue
|
||
participant QR as QueueRunner
|
||
participant LC as LLM Consumer
|
||
participant TC as Tool Consumer
|
||
participant SC as Send Consumer
|
||
participant LLM as DeepSeek API
|
||
participant BT as BuiltinRegistry
|
||
|
||
WX->>WC: 发送消息
|
||
WC->>MQ: enqueue(UserMessage)
|
||
MQ->>QR: dequeue (公平轮转)
|
||
QR->>LC: llm_tx.send(UserMessage)
|
||
LC->>LC: 加载历史/记忆/摘要
|
||
LC->>LLM: chat_stream(上下文)
|
||
LLM-->>LC: 流式回复 + tool_calls
|
||
|
||
alt 有工具调用
|
||
LC->>MQ: enqueue(ToolCall)
|
||
MQ->>QR: dequeue
|
||
QR->>TC: tool_tx.send(ToolCall)
|
||
|
||
alt 高风险工具
|
||
TC->>TC: ApprovalManager.create()
|
||
TC->>MQ: enqueue(LLMReply: 确认码)
|
||
MQ->>QR: dequeue
|
||
QR->>SC: send_tx.send(LLMReply)
|
||
SC->>WC: send_text(确认码)
|
||
WX-->>WC: 回复确认码
|
||
WC->>MQ: enqueue(UserMessage: 确认码)
|
||
MQ->>QR: dequeue
|
||
QR->>LC: llm_tx.send(确认码)
|
||
LC->>TC: ApprovalManager.handle_reply()
|
||
TC-->>LC: Approved
|
||
LC->>MQ: enqueue(ToolCall + approved=true)
|
||
MQ->>QR: dequeue
|
||
QR->>TC: tool_tx.send(ToolCall)
|
||
end
|
||
|
||
TC->>BT: execute(工具名, 参数)
|
||
BT-->>TC: SkillResult
|
||
TC->>MQ: enqueue(ToolResult)
|
||
MQ->>QR: dequeue
|
||
QR->>LC: llm_tx.send(ToolResult)
|
||
LC->>LLM: resume_tool_loop()
|
||
LLM-->>LC: 最终回复
|
||
end
|
||
|
||
LC->>MQ: enqueue(LLMReply)
|
||
MQ->>QR: dequeue
|
||
QR->>SC: send_tx.send(LLMReply)
|
||
SC->>WC: send_text(回复)
|
||
WC->>WX: 显示回复
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 模块依赖关系
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph CORE["核心层"]
|
||
MAIN["main.rs"]
|
||
DAEMON["daemon.rs"]
|
||
CLI["cli.rs"]
|
||
end
|
||
subgraph PIPELINE["消息管线"]
|
||
QUEUE["queue/"]
|
||
CHANNEL["channel/"]
|
||
end
|
||
subgraph AI["AI 层"]
|
||
LLM["llm/"]
|
||
CTX["context/"]
|
||
end
|
||
subgraph TOOL["工具层"]
|
||
TOOLS["tools/"]
|
||
BUILTINS["tools/builtins/"]
|
||
APPROVAL["tools/approval.rs"]
|
||
end
|
||
subgraph IO["IO 层"]
|
||
WECHAT["wechat/"]
|
||
DB["db/"]
|
||
STATE["state.rs"]
|
||
SCHED["scheduler.rs"]
|
||
end
|
||
subgraph UTIL["工具层"]
|
||
LOG["logger.rs"]
|
||
IPC["ipc.rs"]
|
||
WORKER["worker.rs"]
|
||
end
|
||
|
||
MAIN --> CLI & DAEMON & LOG & STATE & DB & CTX & LLM & TOOLS & WECHAT & SCHED
|
||
DAEMON --> QUEUE & CHANNEL & WECHAT & DB & STATE & LLM & CTX & TOOLS & SCHED
|
||
QUEUE --> CHANNEL
|
||
LLM --> CTX & TOOLS
|
||
TOOLS --> BUILTINS & APPROVAL
|
||
CTX --> DB
|
||
TOOLS --> DB
|
||
APPROVAL --> DB
|
||
DB --> STATE
|
||
WORKER --> IPC & LLM & TOOLS
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 数据流(消息生命周期)
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
START(["📱 用户发消息"]) --> A["WeChatClient receive_messages()"]
|
||
A --> B["入库: chat_records (inbound)"]
|
||
B --> C["PipelineMessage kind: UserMessage"]
|
||
C --> D["MessageQueue 公平轮转"]
|
||
D --> E["加载历史消息 (最近20条)"]
|
||
E --> F["加载长期记忆 (MemoryStore)"]
|
||
F --> G["加载摘要 (read_summaries)"]
|
||
G --> H["构建上下文 (builder)"]
|
||
H --> I["Conversation chat_with_tools()"]
|
||
I --> J{"LLM 调用了工具?"}
|
||
J -->|"是"| K["Tool Consumer 执行工具"]
|
||
K --> L{"高风险?→ 审批流程"}
|
||
L --> M["结果回喂 LLM"]
|
||
M --> I
|
||
J -->|"否"| N["LLM 生成最终回复"]
|
||
N --> O["Send Consumer send_text()"]
|
||
O --> P["入库: chat_records (outbound)"]
|
||
P --> ENDD(["📱 用户收到回复"])
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 关键函数调用关系
|
||
|
||
### 5.1 工具执行链路
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph LLM_SIDE["LLM 侧"]
|
||
A["LLM 调用 call_capability()"]
|
||
B["ToolExecutor (闭包)"]
|
||
C["返回 ASYNC_MARKER + 工具名"]
|
||
end
|
||
subgraph QUEUE_SIDE["队列侧"]
|
||
D["PipelineMessage::tool_call()"]
|
||
E["MessageQueue.enqueue()"]
|
||
F["QueueRunner → Tool Consumer"]
|
||
end
|
||
subgraph TOOL_SIDE["工具侧"]
|
||
G["tool_consumer_loop()"]
|
||
H{"高风险?"}
|
||
I["ApprovalManager 审批流程"]
|
||
J["execute_tool()"]
|
||
K{"工具类型?"}
|
||
L["BuiltinRegistry.execute()"]
|
||
M["上下文工具 直接处理"]
|
||
N["unpack_call_params()"]
|
||
end
|
||
subgraph RESULT["结果回喂"]
|
||
O["PipelineMessage::tool_result()"]
|
||
P["LLM Consumer resume_llm_round()"]
|
||
Q["Conversation resume_tool_loop()"]
|
||
end
|
||
|
||
A --> B --> C --> D --> E --> F --> G
|
||
G --> H
|
||
H -->|"是"| I --> J
|
||
H -->|"否"| J
|
||
J --> K
|
||
K -->|"内置工具"| L
|
||
K -->|"上下文工具"| M
|
||
K -->|"call_capability"| N --> K
|
||
J --> O --> P --> Q
|
||
```
|
||
|
||
### 5.2 审批流程
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
A["LLM 请求高风险工具"] --> B["ApprovalManager.create(user_id, tool)"]
|
||
B --> C["生成 6 位确认码 SHA-256 哈希存储"]
|
||
B --> D["创建 oneshot channel"]
|
||
C --> E["发送消息给用户 ⚠️ 操作确认"]
|
||
D --> F["等待用户回复 超时 5 分钟"]
|
||
F --> G{"用户回复"}
|
||
G -->|"匹配确认码"| H["ApprovalDecision::Approved"]
|
||
G -->|"输入 0/取消"| I["ApprovalDecision::Rejected"]
|
||
G -->|"不匹配(最多3次)"| J["减少尝试次数"]
|
||
G -->|"超时"| K["ApprovalDecision::Expired"]
|
||
J --> F
|
||
H --> L["重新入队 UserMessage + approved_tool"]
|
||
I --> M["回复用户: 已取消"]
|
||
K --> N["回复用户: 已超时"]
|
||
```
|
||
|
||
### 5.3 上下文构建流程
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
A["ChatSession (消息历史)"] --> B["estimate_tokens()"]
|
||
B --> C{"Token 超 budget? (默认 28000)"}
|
||
C -->|"是"| D{"checkpoint 之前有未摘要消息?"}
|
||
D -->|"是"| E["trigger_overflow_summary() → 调用 LLM 生成摘要"]
|
||
D -->|"否"| F["裁剪最早的消息 直到符合 budget"]
|
||
C -->|"否"| G{"空闲超时? (12h 无消息)"}
|
||
G -->|"是"| H["trigger_idle_summary() → 清空消息, 保留摘要"]
|
||
G -->|"否"| I["build_context() → 组装最终上下文"]
|
||
E --> I
|
||
F --> I
|
||
H --> I
|
||
I --> J["发送给 LLM"]
|
||
```
|
||
|
||
### 5.3.1 `/clear` 清空上下文流程
|
||
|
||
用户发送 `/clear` 指令时,主动截断上下文并把本次会话归档为摘要(按会话过期处理):
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
U(["📱 用户发送 /clear"]) --> K{"is_clear_command() 匹配?"}
|
||
K -->|"否"| N(["走正常 LLM 对话流程"])
|
||
K -->|"是"| L["加载历史(过滤掉 /clear 本身)"]
|
||
L --> M{"历史非空?"}
|
||
M -->|"否"| R2["回复: 上下文已是空的"]
|
||
M -->|"是"| S["一次性 Conversation + summarizer"]
|
||
S --> T["trigger_clear_summary() → LLM 生成摘要"]
|
||
T --> DB[("session_summaries\nreason=clear")]
|
||
DB --> D["丢弃该用户在途会话 (sessions.retain)"]
|
||
D --> R1["回复确认 + 摘要预览"]
|
||
R2 --> END(["📱 用户收到回复"])
|
||
R1 --> END
|
||
```
|
||
|
||
- **会话边界**:摘要的 `created_at` 作为边界,之后加载历史时通过
|
||
`latest_session_boundary()` 过滤掉此前的消息(`/clear` 与 12h 空闲超时
|
||
共用同一套边界机制)
|
||
- **注入与否**:`/clear` 摘要**不会**注入下一次对话的 system prompt,
|
||
只存档供 `read_summaries` 工具查询(与空闲超时摘要一致)
|
||
- **在途会话**:`/clear` 会丢弃该用户尚未完成的工具往返,避免用旧上下文继续
|
||
|
||
---
|
||
|
||
## 6. 文件依赖矩阵
|
||
|
||
| 文件 | 依赖的关键模块 |
|
||
|------|---------------|
|
||
| `main.rs` | cli, daemon, db, llm, context, tools, wechat, state, scheduler, logger |
|
||
| `daemon.rs` | queue, channel, wechat, db, state, llm, context, tools, scheduler |
|
||
| `cli.rs` | clap (独立) |
|
||
| `llm/conversation.rs` | context/builder, context/types, llm/provider, llm/types |
|
||
| `llm/deepseek.rs` | llm/provider, llm/types |
|
||
| `llm/provider.rs` | llm/types |
|
||
| `llm/types.rs` | serde (独立) |
|
||
| `context/types.rs` | llm/types |
|
||
| `context/builder.rs` | context/types, llm/types |
|
||
| `context/tools.rs` | db/models |
|
||
| `queue/message_queue.rs` | channel/mod |
|
||
| `queue/runner.rs` | queue/message_queue |
|
||
| `tools/builtin.rs` | tools/types, tools/builtins/* |
|
||
| `tools/approval.rs` | db/models |
|
||
| `tools/mod.rs` | tools/types |
|
||
| `wechat/client.rs` | wechat/types |
|
||
| `wechat/types.rs` | serde (独立) |
|
||
| `db/mod.rs` | db/models |
|
||
| `db/models.rs` | sqlx (独立) |
|
||
| `state.rs` | serde (独立) |
|
||
| `scheduler.rs` | db/models |
|
||
| `worker.rs` | ipc, llm, tools (已废弃) |
|
||
| `ipc.rs` | serde (已废弃) |
|
||
|
||
---
|
||
|
||
## 7. 启动流程
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
START(["ias listen --llm"]) --> A["main()"]
|
||
A --> B["clap::Parser::parse()"]
|
||
B --> C{"命令类型"}
|
||
C -->|"Tool"| D["cmd_tool() 直接执行工具"]
|
||
C -->|"Login"| E["cmd_login() 扫码登录"]
|
||
C -->|"Listen/Service"| F["cmd_listen()"]
|
||
|
||
F --> G["加载 .env"]
|
||
F --> H["Database::connect()"]
|
||
H --> I{"数据库可用?"}
|
||
I -->|"是"| J["创建 MemoryStore (DB 模式)"]
|
||
I -->|"否"| K["创建 MemoryStore (文件模式)"]
|
||
|
||
F --> L["加载认证信息"]
|
||
L --> M{"已登录?"}
|
||
M -->|"否"| N["报错退出"]
|
||
M -->|"是"| O["WeChatClient.set_auth()"]
|
||
|
||
O --> P["WeChatClient.notify_start()"]
|
||
P --> Q["创建 ApprovalManager"]
|
||
Q --> R["创建 Conversation + ToolExecutor"]
|
||
R --> S["启动 Scheduler (定时任务)"]
|
||
S --> T["启动 listen_loop()"]
|
||
|
||
T --> U["WeChatClient.receive_messages()"]
|
||
U --> V{"收到消息?"}
|
||
V -->|"是"| W["enqueue PipelineMessage"]
|
||
W --> X["LLM/Tool/Send 消费者处理"]
|
||
V -->|"否/超时"| U
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 消费者架构(三消费者)
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph QUEUE2["消息队列"]
|
||
Q["MessageQueue 公平轮转"]
|
||
R["QueueRunner 路由"]
|
||
end
|
||
subgraph LLM_C["LLM Consumer"]
|
||
L1["接收: UserMessage / ToolResult / ScheduledTask"]
|
||
L2["加载用户历史 + 记忆 + 摘要"]
|
||
L3["创建/恢复 Conversation"]
|
||
L4["chat_with_tools() / resume_tool_loop()"]
|
||
L5{"有异步工具等待?"}
|
||
L6["发送 LLMReply"]
|
||
L7["保留 session"]
|
||
L8["清理 session"]
|
||
end
|
||
subgraph TOOL_C["Tool Consumer"]
|
||
T1["接收: ToolCall / ApprovalRequest"]
|
||
T2{"高风险 + 未审批?"}
|
||
T3["ApprovalManager.create()"]
|
||
T4["发送确认码"]
|
||
T5["execute_tool()"]
|
||
T6["发送 ToolResult"]
|
||
end
|
||
subgraph SEND_C["Send Consumer"]
|
||
S1["接收: LLMReply"]
|
||
S2["WeChatClient.send_text()"]
|
||
S3["入库: chat_records (outbound)"]
|
||
end
|
||
|
||
Q --> R
|
||
R -->|"UserMessage / ToolResult"| L1
|
||
R -->|"ToolCall"| T1
|
||
R -->|"LLMReply"| S1
|
||
L1 --> L2 --> L3 --> L4
|
||
L4 --> L5
|
||
L5 -->|"是"| L7
|
||
L5 -->|"否"| L6
|
||
``` |