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:
@@ -0,0 +1,92 @@
|
||||
use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/// 长期记忆管理器(内存 + PostgreSQL 双写)
|
||||
pub struct MemoryStore {
|
||||
pool: Option<Arc<PgPool>>,
|
||||
cache: Arc<Mutex<Vec<String>>>,
|
||||
}
|
||||
|
||||
impl MemoryStore {
|
||||
pub fn new(pool: Option<Arc<PgPool>>) -> Self {
|
||||
Self {
|
||||
pool,
|
||||
cache: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// 初始化:从数据库加载记忆到缓存
|
||||
pub async fn load(&self) {
|
||||
let pool = match self.pool.as_ref() {
|
||||
Some(p) => p.clone(),
|
||||
None => return,
|
||||
};
|
||||
if let Ok(rows) = sqlx::query_as::<_, (String,)>(
|
||||
"SELECT content FROM user_memories WHERE user_id = 'default' ORDER BY id",
|
||||
)
|
||||
.fetch_all(pool.as_ref())
|
||||
.await
|
||||
{
|
||||
let mut cache = self.cache.lock().await;
|
||||
*cache = rows.into_iter().map(|(c,)| c).collect();
|
||||
}
|
||||
}
|
||||
|
||||
/// 读取所有记忆
|
||||
pub async fn read(&self) -> String {
|
||||
let mems = self.cache.lock().await;
|
||||
if mems.is_empty() {
|
||||
"暂无长期记忆".to_string()
|
||||
} else {
|
||||
mems.iter()
|
||||
.enumerate()
|
||||
.map(|(i, m)| format!("{}. {}", i + 1, m))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
/// 写入一条记忆
|
||||
pub async fn write(&self, content: &str) -> String {
|
||||
// 写入缓存
|
||||
self.cache.lock().await.push(content.to_string());
|
||||
// 写入数据库
|
||||
if let Some(ref pool) = self.pool {
|
||||
let _ = sqlx::query(
|
||||
"INSERT INTO user_memories (user_id, content) VALUES ('default', $1)",
|
||||
)
|
||||
.bind(content)
|
||||
.execute(pool.as_ref())
|
||||
.await;
|
||||
}
|
||||
"已添加长期记忆".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// read_summaries 工具:读取历史摘要
|
||||
pub async fn read_summaries(
|
||||
session: &Arc<Mutex<super::types::ChatSession>>,
|
||||
) -> String {
|
||||
let s = session.lock().await;
|
||||
if s.summaries.is_empty() {
|
||||
"暂无历史摘要".to_string()
|
||||
} else {
|
||||
s.summaries
|
||||
.iter()
|
||||
.map(|sum| {
|
||||
let reason = match sum.reason {
|
||||
super::types::SummaryReason::Overflow => "上下文压缩",
|
||||
super::types::SummaryReason::Timeout => "空闲超时",
|
||||
};
|
||||
format!(
|
||||
"[{}] {} (原因: {})",
|
||||
sum.created_at.format("%Y-%m-%d %H:%M"),
|
||||
sum.text,
|
||||
reason,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n---\n")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user