PicoBot/src/storage/records.rs
oudecheng 414105d419 feat(model): 话题级模型选择持久化与 API(topics 表加 provider/model 列 + 双写内存缓存)
- topics 表迁移新增 provider/model 列,记录话题级显式模型选择
- storage 层新增 update_topic_model / list_topic_model_selections
- 新增 POST /api/topic/select-model 与 GET /api/topic/selected-model
- 双写 key 取 topic 行自带 session_id(不信任请求体,防污染其他会话)
- 清除时同步清 session 级选择,避免'重置为默认'被物化逻辑重新覆盖
2026-08-15 15:33:58 +08:00

291 lines
8.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
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>,
}