- 解析: OpenAIUsage 支持 DeepSeek prompt_cache_hit_tokens + OpenAI prompt_tokens_details.cached_tokens fallback;Anthropic 接入 cache_read_input_tokens - 存储: messages 表新增 cached_tokens 列(幂等迁移),贯穿 INSERT/SELECT/聚合 - 解耦加固: 抽取 MESSAGE_LOAD_COLUMNS 收敛 6 处消息加载 SELECT;抽取 USAGE_SUM_COLUMNS + read_usage_sum_row 收敛 topic/session 两份重复 SUM 聚合 - 协议: SessionTokenStats/TopicTokenStats 新增 cached_tokens 字段 - 前端: 面板新增缓存命中行 + 缓存命中率进度条(命中率越高越绿,与 ctx 占用方向相反)
293 lines
8.7 KiB
Rust
293 lines
8.7 KiB
Rust
use serde::{Deserialize, Serialize};
|
||
|
||
/// 全局统一的记忆 scope_key,所有渠道共享同一份记忆空间
|
||
pub const GLOBAL_SCOPE_KEY: &str = "default";
|
||
|
||
/// 允许的记忆命名空间列表
|
||
///
|
||
/// 每个命名空间代表一类记忆内容,用于分类管理和检索。
|
||
/// 禁止使用未在此列表中的 namespace 创建记忆。
|
||
pub const ALLOWED_MEMORY_NAMESPACES: &[(&str, &str)] = &[
|
||
(
|
||
"user",
|
||
"用户记忆:存储用户长期偏好、身份背景和历史协作信息,实现跨会话的个性化服务与持续协作",
|
||
),
|
||
(
|
||
"semantic",
|
||
"语义记忆:存储结构化或非结构化知识内容,支持知识检索、问答增强和长期知识积累",
|
||
),
|
||
(
|
||
"episodic",
|
||
"情景记忆:记录历史对话、任务执行过程及关键事件,支持经验回溯、案例复用和行为追踪",
|
||
),
|
||
(
|
||
"skill",
|
||
"技能记忆:存储技能定义、工作流、工具调用策略及最佳实践,支持能力复用与自动化执行",
|
||
),
|
||
(
|
||
"environment",
|
||
"环境记忆:存储外部系统状态、运行环境配置和实时资源信息,为智能决策提供环境感知能力",
|
||
),
|
||
(
|
||
"reflection",
|
||
"反思记忆:沉淀任务执行过程中的成功经验、失败原因和优化建议,支持智能体持续学习与自我改进",
|
||
),
|
||
("other", "其他记忆:不属于以上分类的其他记忆内容"),
|
||
];
|
||
|
||
/// 验证 namespace 是否在允许列表中
|
||
pub fn is_valid_namespace(namespace: &str) -> bool {
|
||
ALLOWED_MEMORY_NAMESPACES
|
||
.iter()
|
||
.any(|(name, _)| *name == namespace)
|
||
}
|
||
|
||
/// 获取 namespace 的中文描述
|
||
pub fn get_namespace_description(namespace: &str) -> Option<&'static str> {
|
||
ALLOWED_MEMORY_NAMESPACES
|
||
.iter()
|
||
.find(|(name, _)| *name == namespace)
|
||
.map(|(_, desc)| *desc)
|
||
}
|
||
|
||
/// 获取所有允许的 namespace 名称列表(用于 JSON schema enum)
|
||
pub fn allowed_namespace_names() -> Vec<&'static str> {
|
||
ALLOWED_MEMORY_NAMESPACES
|
||
.iter()
|
||
.map(|(name, _)| *name)
|
||
.collect()
|
||
}
|
||
|
||
#[derive(Debug, Clone)]
|
||
pub struct TodoRecord {
|
||
pub id: String,
|
||
pub scope_key: String,
|
||
pub session_id: String,
|
||
pub topic_id: Option<String>,
|
||
pub content: String,
|
||
pub status: String,
|
||
pub priority: String,
|
||
pub created_at: i64,
|
||
pub updated_at: i64,
|
||
pub created_by_message_id: Option<String>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct SkillEventRecord {
|
||
pub id: String,
|
||
pub session_id: Option<String>,
|
||
pub event_type: String,
|
||
pub skill_name: Option<String>,
|
||
pub payload: serde_json::Value,
|
||
pub created_at: i64,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct SessionRecord {
|
||
pub id: String,
|
||
pub title: String,
|
||
pub channel_name: String,
|
||
pub chat_id: String,
|
||
pub summary: Option<String>,
|
||
pub created_at: i64,
|
||
pub updated_at: i64,
|
||
pub last_active_at: i64,
|
||
pub archived_at: Option<i64>,
|
||
pub deleted_at: Option<i64>,
|
||
pub message_count: i64,
|
||
pub user_turn_count: i64,
|
||
pub agent_prompt_reinjection_count: i64,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct TopicRecord {
|
||
pub id: String,
|
||
pub session_id: String,
|
||
pub title: String,
|
||
pub description: Option<String>,
|
||
pub created_at: i64,
|
||
pub updated_at: i64,
|
||
pub last_active_at: i64,
|
||
pub message_count: i64,
|
||
/// 话题级用户模型选择(NULL 表示无显式选择,运行时按继承链解析)
|
||
pub provider: Option<String>,
|
||
pub model: Option<String>,
|
||
}
|
||
|
||
/// pending_subagents 表的记录,跟踪异步子代理执行状态。
|
||
///
|
||
/// 生命周期:task 工具 spawn 时插入(status=running),
|
||
/// 子代理完成时更新为 completed/failed/timeout,
|
||
/// 进程重启时 running 状态被标记为 interrupted。
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct PendingSubagentRecord {
|
||
/// 子代理 task_id(主键,与 TaskSession.id 一致)
|
||
pub task_id: String,
|
||
/// 父会话 session_id
|
||
pub parent_session_id: String,
|
||
/// 父会话 topic_id(用于按 topic 查询未完成子代理)
|
||
pub parent_topic_id: String,
|
||
/// 父会话 chat_id
|
||
pub parent_chat_id: String,
|
||
/// 父会话 channel_name
|
||
pub parent_channel: String,
|
||
/// 子代理定义名称(可选,用于诊断)
|
||
pub def_name: Option<String>,
|
||
/// 子代理启动时间戳
|
||
pub spawned_at: i64,
|
||
/// 执行状态:running / completed / failed / interrupted / cancelled / timeout
|
||
pub status: String,
|
||
}
|
||
|
||
/// 单个 session 的 token 用量统计(聚合结果)。
|
||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||
pub struct SessionTokenStats {
|
||
pub prompt_tokens: u64,
|
||
pub completion_tokens: u64,
|
||
pub total_tokens: u64,
|
||
/// 累计缓存命中的输入 tokens 数(老数据为 0)
|
||
pub cached_tokens: u64,
|
||
pub last_prompt_tokens: Option<u32>,
|
||
pub context_window_tokens: Option<u32>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct MemoryRecord {
|
||
pub id: String,
|
||
pub scope_kind: String,
|
||
pub scope_key: String,
|
||
pub namespace: String,
|
||
pub memory_key: String,
|
||
pub content: String,
|
||
pub source_type: String,
|
||
pub source_session_id: Option<String>,
|
||
pub source_message_id: Option<String>,
|
||
pub source_message_seq: Option<i64>,
|
||
pub source_channel_name: Option<String>,
|
||
pub source_chat_id: Option<String>,
|
||
pub created_at: i64,
|
||
pub updated_at: i64,
|
||
}
|
||
|
||
#[derive(Debug, Clone)]
|
||
pub struct MemoryUpsert {
|
||
pub scope_kind: String,
|
||
pub scope_key: String,
|
||
pub namespace: String,
|
||
pub memory_key: String,
|
||
pub content: String,
|
||
pub source_type: String,
|
||
pub source_session_id: Option<String>,
|
||
pub source_message_id: Option<String>,
|
||
pub source_message_seq: Option<i64>,
|
||
pub source_channel_name: Option<String>,
|
||
pub source_chat_id: Option<String>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "snake_case")]
|
||
pub enum SchedulerJobState {
|
||
Scheduled,
|
||
Running,
|
||
Paused,
|
||
Completed,
|
||
}
|
||
|
||
impl SchedulerJobState {
|
||
pub fn as_str(&self) -> &'static str {
|
||
match self {
|
||
SchedulerJobState::Scheduled => "scheduled",
|
||
SchedulerJobState::Running => "running",
|
||
SchedulerJobState::Paused => "paused",
|
||
SchedulerJobState::Completed => "completed",
|
||
}
|
||
}
|
||
|
||
pub fn from_str(value: &str) -> Option<Self> {
|
||
match value {
|
||
"scheduled" => Some(Self::Scheduled),
|
||
"running" => Some(Self::Running),
|
||
"paused" => Some(Self::Paused),
|
||
"completed" => Some(Self::Completed),
|
||
_ => None,
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "snake_case")]
|
||
pub enum SchedulerJobStatus {
|
||
Ok,
|
||
Error,
|
||
Skipped,
|
||
}
|
||
|
||
impl SchedulerJobStatus {
|
||
pub fn as_str(&self) -> &'static str {
|
||
match self {
|
||
SchedulerJobStatus::Ok => "ok",
|
||
SchedulerJobStatus::Error => "error",
|
||
SchedulerJobStatus::Skipped => "skipped",
|
||
}
|
||
}
|
||
|
||
pub fn from_str(value: &str) -> Option<Self> {
|
||
match value {
|
||
"ok" => Some(Self::Ok),
|
||
"error" => Some(Self::Error),
|
||
"skipped" => Some(Self::Skipped),
|
||
_ => None,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Default for SchedulerJobState {
|
||
fn default() -> Self {
|
||
Self::Scheduled
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct SchedulerJobRecord {
|
||
pub id: String,
|
||
pub kind: String,
|
||
pub schedule: serde_json::Value,
|
||
pub interval_secs: i64,
|
||
pub startup_delay_secs: i64,
|
||
pub target: serde_json::Value,
|
||
pub payload: serde_json::Value,
|
||
pub enabled: bool,
|
||
pub state: SchedulerJobState,
|
||
pub last_status: Option<SchedulerJobStatus>,
|
||
pub last_error: Option<String>,
|
||
pub run_count: i64,
|
||
pub max_runs: Option<i64>,
|
||
pub last_fired_at: Option<i64>,
|
||
pub next_fire_at: Option<i64>,
|
||
pub paused_at: Option<i64>,
|
||
pub completed_at: Option<i64>,
|
||
pub created_at: i64,
|
||
pub updated_at: i64,
|
||
}
|
||
|
||
#[derive(Debug, Clone)]
|
||
pub struct SchedulerJobUpsert {
|
||
pub id: String,
|
||
pub kind: String,
|
||
pub schedule: serde_json::Value,
|
||
pub interval_secs: i64,
|
||
pub startup_delay_secs: i64,
|
||
pub target: serde_json::Value,
|
||
pub payload: serde_json::Value,
|
||
pub enabled: bool,
|
||
pub state: SchedulerJobState,
|
||
pub last_status: Option<SchedulerJobStatus>,
|
||
pub last_error: Option<String>,
|
||
pub run_count: i64,
|
||
pub max_runs: Option<i64>,
|
||
pub last_fired_at: Option<i64>,
|
||
pub next_fire_at: Option<i64>,
|
||
pub paused_at: Option<i64>,
|
||
pub completed_at: Option<i64>,
|
||
}
|