feat: iPet → iAs 完整迁移,Rust 版微信 AI 助手

Phase 1  CLI 框架 + 配置系统
  - clap 子命令: login / listen / send / whoami / usage
  - config.json + env var 替换
  - tracing 日志系统
  - state 持久化(auth/runtime 文件存 + PostgreSQL)

Phase 2  微信通道
  - wechat::client — 完整 iLink Bot HTTP API 实现
  - 扫码登录(终端二维码 + 轮询状态)
  - 长轮询 getupdates / 消息收发 / 监听注册

Phase 3  AI 对话(纯文本 + function calling)
  - LlmProvider trait: DeepSeek + LM Studio 实现
  - SSE 流式解析(text / reasoning / tool_calls delta / usage)
  - Conversation: 消息历史 + chat / chat_with_tools 工具循环

Phase 4  PostgreSQL 集成
  - app_state(认证 KV 存储)
  - chat_records(消息收发记录)
  - llm_usage(Token 用量统计缓存命中率)
  - user_memories(长期记忆持久化)
  - pending_approvals(审批确认码)
  - scheduled_tasks(定时任务表)

Phase 5  一切皆 Skill(工具系统)
  - SkillRegistry: 系统 + 用户 skills 双目录合并
  - SKILL.md 解析器 + 子进程执行器(stdin JSON → stdout)
  - 9 个系统 Skills: datetime / weather / search / email /
    shell / schedule / memos / read_memories / read_summaries
  - ApprovalManager: High 风险技能 → 确认码审批(透明模式)
  - High 风险技能:确认码审批(透明模式)

Phase 6  定时任务调度器

上下文管理
  - ChatSession: checkpoint + token budget (28K) + summaries
  - Token 估算器(中英文自适应)
  - 12h 空闲 → trigger_idle_summary(不入会话)
  - Budget 溢出 → trigger_overflow_summary(入会话 + drain 旧消息)
  - Summarizer: LLM 生成自然语言摘要(fallback 简单截断)
  - 长期记忆 / 摘要 通过 read_memories / read_summaries 工具按需读取

工具调用日志 + Token 统计
  - INFO: 工具名 + 参数 + 结果摘要
  - DEBUG: 子进程 exit/stdout/stderr
  - ias usage --since --until --model 查看用量和缓存命中率
This commit is contained in:
2026-06-01 17:21:43 +08:00
parent 3a2d2769b5
commit b9de3665d9
53 changed files with 9874 additions and 2 deletions
+328
View File
@@ -0,0 +1,328 @@
# iAs 工具系统设计
## 核心理念
**一切皆 Skill。**
没有"内置工具"和"外部技能"的区别——所有工具都是 SKILL.md,区别仅在来源:
- **系统 skills** — `resources/skills/` 目录,随 iAs 分发,只读
- **用户 skills** — `skills/` 项目目录,用户自己添加
加载时自动合并,同名时用户 skills 覆盖系统 skills。
---
## 架构
```
┌──────────────────────────────────────────────────────────┐
│ LLM │
│ generate_reply() ←→ tool_choice → tool_execute(result) │
└─────────────────────┬────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ SkillRegistry │
│ │
│ ┌──────────────────┐ ┌────────────────────────────┐ │
│ │ 系统 Skills │ │ 用户 Skills │ │
│ │ resources/skills/ │ │ skills/ │ │
│ │ │ │ │ │
│ │ • datetime │ │ • query_qweather/ │ │
│ │ • weather │ │ • search_tavily/ │ │
│ │ • search_web │ │ • start_pc/ │ │
│ │ • email │ │ • ... │ │
│ │ • shell_exec │ │ │ │
│ │ • schedule │ │ │ │
│ └────────┬──────────┘ └────────────┬──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Approval Gate │ │
│ │ risk == High → 确认码审批 / Low → 自动执行 │ │
│ │ 审批过程对 LLM 完全透明 │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
```
---
## 核心类型
```rust
/// 风险等级(仅两级)
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RiskLevel {
Low, // 只读/安全,自动执行
High, // 写操作/有风险,需微信确认码审批
}
/// 技能来源
#[derive(Debug, Clone, PartialEq)]
pub enum SkillSource {
System, // resources/skills/
User, // skills/
}
/// 技能元数据(从 SKILL.md 解析)
#[derive(Debug, Clone, Deserialize)]
pub struct SkillSpec {
pub name: String,
pub description: String,
pub risk_level: RiskLevel,
pub parameters: serde_json::Value, // JSON Schema
pub timeout_secs: u64,
}
/// 技能(加载到内存后)
pub struct Skill {
pub spec: SkillSpec,
pub source: SkillSource,
pub dir: PathBuf,
pub execute_command: String,
}
```
---
## SKILL.md 格式
每个技能是一个目录,包含 `SKILL.md` 文件:
```markdown
# Get Current Datetime
获取当前日期时间(北京时间 UTC+8)。
## Risk Level
Low
## Parameters
\`\`\`json
{
"type": "object",
"properties": {
"format": {
"type": "string",
"description": "时间格式: full | date | time",
"enum": ["full", "date", "time"],
"default": "full"
}
}
}
\`\`\`
## Execute
\`\`\`bash
#!/usr/bin/env bash
scripts/datetime.sh
\`\`\`
```
---
## 内置 Skills 一览
随 iAs 分发的系统 skills
| Skill | 风险 | 说明 |
|-------|------|------|
| `get_current_datetime` | Low | 获取当前时间(UTC+8 |
| `query_weather` | Low | 和风天气查询 |
| `search_web` | Low | Tavily 网络搜索 |
| `email` | High | 收发邮件 |
| `execute_shell` | High | 执行 shell 命令 |
| `list_scheduled_tasks` | Low | 列出定时任务 |
| `manage_scheduled_task` | High | 增删改定时任务 |
---
## SkillRegistry
```rust
pub struct SkillRegistry {
skills: HashMap<String, Arc<Skill>>,
}
impl SkillRegistry {
pub async fn load(system_dir: &str, user_dir: &str) -> Result<Self, LoadError>;
pub fn list_specs(&self) -> Vec<&SkillSpec>;
pub fn list_specs_by_risk(&self, level: RiskLevel) -> Vec<&SkillSpec>;
pub fn get(&self, name: &str) -> Option<&Skill>;
/// 执行技能(含审批流程)
pub async fn execute(
&self,
name: &str,
params: serde_json::Value,
ctx: &ExecutionContext,
) -> Result<SkillResult, SkillError>;
}
pub struct ExecutionContext {
pub user_id: String,
pub db: Option<Arc<Database>>,
/// 发送微信消息的回调
pub send_wechat: Option<Box<dyn Fn(&str, &str) -> Result<(), String> + Send + Sync>>,
/// 等待用户回复的回调(返回用户消息文本)
pub wait_for_reply: Option<Box<dyn Fn(&str, Duration) -> Result<Option<String>, String> + Send + Sync>>,
}
```
---
## 审批流程(透明模式)
**核心原则:审批过程对 LLM 完全不可见。** LLM 只看到最终结果。
```
LLM 调用 High 风险技能
SkillRegistry::execute()
├── 查找 Skill
├── risk_level == High
└── YES ──────────────────────────────────────────┐
│ │
▼ │
┌──────────────────────────────────────────┐ │
│ 等待阶段(LLM 不感知) │ │
│ │ │
│ ① 生成 6 位确认码(如 482731) │ │
│ ② SHA-256 哈希存入 pending_approvals │ │
│ ③ 通过微信发送确认消息给用户: │ │
│ │ │
│ ┌─────────────────────────────┐ │ │
│ │ ⚠️ 操作确认 │ │ │
│ │ │ │ │
│ │ 技能:send_email │ │ │
│ │ 参数:收件人 xxx@xxx.com │ │ │
│ │ │ │ │
│ │ 确认码:482731 │ │ │
│ │ 回复确认码以继续 │ │ │
│ │ 回复 0 或「取消」以取消 │ │ │
│ │ (5分钟内有效,最多3次尝试) │ │ │
│ └─────────────────────────────┘ │ │
│ │ │
│ ④ 等待用户回复 │ │
│ │ │ │
│ ├── 回复正确确认码 ──┐ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────┐ │ │
│ │ │ ⑤ 执行技能 │ │ │
│ │ │ ⑥ LLM 看到 │ │ │
│ │ │ 执行结果 │ │ │
│ │ └──────────────┘ │ │
│ │ │ │
│ ├── 回复错误确认码 │ │
│ │ → "确认码错误,还剩 N 次" │ │
│ │ → 重试(最多 3 次) │ │
│ │ → 3 次都错 │ │
│ │ → LLM 看到: │ │
│ │ "用户拒绝了你的调用: │ │
│ │ send_email" │ │
│ │ │ │
│ ├── 回复 0 / 「取消」 │ │
│ │ → LLM 看到: │ │
│ │ "用户拒绝了你的调用: │ │
│ │ send_email" │ │
│ │ │ │
│ └── 5 分钟超时 │ │
│ → LLM 看到: │ │
│ "用户没有确认操作: │ │
│ send_email" │ │
└──────────────────────────────────────────┘
```
### LLM 视角(两种可能)
```
✅ 通过:
LLM: 调用 send_email({to: "xxx", subject: "..."})
系统: { success: true, message: "邮件已发送" }
❌ 拒绝:
LLM: 调用 send_email({to: "xxx", subject: "..."})
系统: { success: false, error: "用户拒绝了你的调用:send_email" }
```
LLM 不知道确认码的存在,不知道审批过程。它只知道自己调用了工具,然后得到了结果或拒绝。
---
## 数据库表
```sql
-- 待审批的工具调用
CREATE TABLE pending_approvals (
id UUID PRIMARY KEY,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL, -- 5分钟后过期
user_id TEXT NOT NULL,
skill_name TEXT NOT NULL,
params JSONB NOT NULL,
code_hash TEXT NOT NULL, -- 确认码 SHA-256(不存明文)
attempts_left INTEGER NOT NULL DEFAULT 3, -- 剩余尝试次数
status TEXT NOT NULL DEFAULT 'pending', -- pending | approved | rejected | expired
result JSONB,
consumed_at TIMESTAMPTZ
);
```
---
## Skills 目录结构
```
resources/skills/ # 系统内置 Skills(随 iAs 分发)
├── get_current_datetime/
│ ├── SKILL.md
│ └── scripts/datetime.sh
├── query_weather/
│ ├── SKILL.md
│ └── scripts/weather.sh
├── search_web/
│ ├── SKILL.md
│ └── scripts/search.sh
├── email/
│ ├── SKILL.md
│ └── scripts/email.sh
├── execute_shell/
│ ├── SKILL.md
│ └── scripts/shell.sh
├── list_scheduled_tasks/
│ ├── SKILL.md
│ └── scripts/list.sh
└── manage_scheduled_task/
├── SKILL.md
└── scripts/manage.sh
skills/ # 用户 Skills(用户自行添加)
├── query_qweather/
│ ├── SKILL.md
│ └── scripts/query.sh
├── search_tavily/
│ ├── SKILL.md
│ └── scripts/search.sh
└── start_pc/
├── SKILL.md
└── scripts/wake.sh
```
---
## 实施步骤
| 步骤 | 内容 | 预估 |
|------|------|------|
| Step 1 | SkillSpec/Skill/SkillRegistry 核心类型 + 注册表 | 半天 |
| Step 2 | SKILL.md 解析器 + 目录扫描(system + user 合并) | 半天 |
| Step 3 | 技能执行器:子进程管理 + 参数 JSON 传递 + 结果解析 | 半天 |
| Step 4 | 系统 Skills 脚本实现(7 个 bash 脚本) | 1 天 |
| Step 5 | 审批系统:pending_approvals 表 + 确认码生成/验证 + 微信透明通知 | 1 天 |
| Step 6 | LLM function calling 集成 + 审计日志 | 1 天 |