23c7fbac62
为整个项目添加了详尽的中文文档注释,覆盖所有模块: 入口与架构: - main.rs — 架构图、数据流、4 个关键设计决策 - cli.rs — 所有 CLI 子命令中文化 - Cargo.toml — 每个依赖的作用说明 - channel/mod.rs — 渠道标识设计意图 核心守护进程与消息队列: - daemon.rs — 三消费者架构图、审批流 - queue/* — 公平轮转算法、路由调度架构 LLM 层: - types.rs — Role/Message/ToolCall 等每个字段含义 - provider.rs — Provider trait 设计 + SSE 解析 - conversation.rs — 工具循环流程图、摘要机制 - deepseek.rs — API 请求格式 上下文管理: - context/types.rs — ChatSession 消息生命周期 - context/builder.rs — Token 预算管理策略 - context/tools.rs — MemoryStore 双写策略 工具系统: - tools/mod.rs — 两层元工具架构图 - tools/approval.rs — 审批流程 - tools/definitions.rs — 声明式工具三要素 - tools/subprocess.rs — 执行流程 + 缓存策略 数据库与状态: - db/* — 5 张表用途、回退策略 - state.rs — 文件存储回退方案 - scheduler.rs — SKIP LOCKED 防重复 已废弃模块: - ipc.rs — 旧 UDS 协议 - worker.rs — 旧 vs 新架构对比
113 lines
3.9 KiB
Rust
113 lines
3.9 KiB
Rust
use sqlx::PgPool;
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use tokio::sync::Mutex;
|
|
|
|
/// ## 长期记忆管理器(内存 + PostgreSQL 双写)
|
|
///
|
|
/// 为每个用户维护独立的长期记忆列表,支持读写操作。
|
|
///
|
|
/// ### 存储策略
|
|
/// * **内存缓存** — `HashMap<String, Vec<String>>`,按 user_id 隔离
|
|
/// * **数据库持久化** — 可选的 `PgPool`,写入时同时写到 PostgreSQL
|
|
///
|
|
/// ### 数据流
|
|
/// ```text
|
|
/// 启动时 load(user_id) → 从数据库加载到缓存
|
|
/// 对话中 read_for() → 从缓存读取,返回 "1. ...\n2. ..." 格式
|
|
/// 对话中 write_for() → 写入缓存 + 写入数据库
|
|
/// ```
|
|
pub struct MemoryStore {
|
|
pool: Option<Arc<PgPool>>,
|
|
cache: Arc<Mutex<HashMap<String, Vec<String>>>>,
|
|
}
|
|
|
|
impl MemoryStore {
|
|
pub fn new(pool: Option<Arc<PgPool>>) -> Self {
|
|
Self {
|
|
pool,
|
|
cache: Arc::new(Mutex::new(HashMap::new())),
|
|
}
|
|
}
|
|
|
|
pub async fn load(&self, user_id: &str) {
|
|
let pool = match self.pool.as_ref() {
|
|
Some(p) => p.clone(),
|
|
None => return,
|
|
};
|
|
let uid = if user_id.is_empty() { "default" } else { user_id };
|
|
if let Ok(rows) = sqlx::query_as::<_, (String,)>(
|
|
"SELECT content FROM user_memories WHERE user_id = $1 ORDER BY id",
|
|
)
|
|
.bind(uid)
|
|
.fetch_all(pool.as_ref())
|
|
.await
|
|
{
|
|
let mems: Vec<String> = rows.into_iter().map(|(c,)| c).collect();
|
|
self.cache.lock().await.insert(uid.to_string(), mems);
|
|
}
|
|
}
|
|
|
|
pub async fn read_for(&self, user_id: &str) -> String {
|
|
let uid = if user_id.is_empty() { "default" } else { user_id };
|
|
let cache = self.cache.lock().await;
|
|
match cache.get(uid) {
|
|
Some(m) if !m.is_empty() => m.iter().enumerate()
|
|
.map(|(i, v)| format!("{}. {}", i + 1, v))
|
|
.collect::<Vec<_>>()
|
|
.join("\n"),
|
|
_ => "暂无长期记忆".to_string(),
|
|
}
|
|
}
|
|
|
|
pub async fn write_for(&self, user_id: &str, content: &str) -> String {
|
|
let uid = if user_id.is_empty() { "default" } else { user_id };
|
|
self.cache.lock().await.entry(uid.to_string()).or_default().push(content.to_string());
|
|
if let Some(ref pool) = self.pool {
|
|
let _ = sqlx::query("INSERT INTO user_memories (user_id, content) VALUES ($1, $2)")
|
|
.bind(uid)
|
|
.bind(content)
|
|
.execute(pool.as_ref())
|
|
.await;
|
|
}
|
|
"已添加长期记忆".to_string()
|
|
}
|
|
}
|
|
|
|
/// ## `read_summaries` 工具 —— 读取历史摘要(内存 + 数据库)
|
|
///
|
|
/// 返回当前 session 中的摘要列表 + 数据库中存档的摘要。
|
|
/// 去重:数据库中的摘要如果与 session 中的摘要前 30 个字符相同,则跳过。
|
|
pub async fn read_summaries(
|
|
session: &Arc<Mutex<super::types::ChatSession>>,
|
|
) -> String {
|
|
let s = session.lock().await;
|
|
let mut lines: Vec<String> = Vec::new();
|
|
|
|
for sum in &s.summaries {
|
|
let reason = match sum.reason {
|
|
super::types::SummaryReason::Overflow => "上下文压缩",
|
|
super::types::SummaryReason::Timeout => "空闲超时",
|
|
};
|
|
lines.push(format!(
|
|
"[{}] {} (原因: {})",
|
|
sum.created_at.format("%Y-%m-%d %H:%M"),
|
|
sum.text,
|
|
reason,
|
|
));
|
|
}
|
|
|
|
if let Some(pool) = &s.db_pool {
|
|
let uid = if s.current_user_id.is_empty() { "default" } else { &s.current_user_id };
|
|
if let Ok(db_summaries) = crate::db::models::load_summaries(pool, uid, 5).await {
|
|
for text in db_summaries {
|
|
if !lines.iter().any(|l| l.contains(&text.chars().take(30).collect::<String>())) {
|
|
lines.push(format!("[数据库存档] {}", text));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if lines.is_empty() { "暂无历史摘要".to_string() } else { lines.join("\n---\n") }
|
|
}
|