refactor: 移除文件状态并修复工具上下文关联

This commit is contained in:
2026-06-16 20:18:34 +08:00
parent 1d78567102
commit f608c27f8a
9 changed files with 552 additions and 402 deletions
+299 -129
View File
@@ -39,12 +39,14 @@
//! 采用统一的单进程 tokio task 架构。旧版 daemon+worker 分离模式(UDS IPC)已在 v2 中移除。
use crate::channel::ChannelId;
use crate::context::HistoryEntry;
use crate::context::MemoryStore;
use crate::db::Database;
use crate::context::HistoryEntry;
use crate::llm::{ChatResult, Conversation, ConversationConfig, ToolExecutor, DEFAULT_SYSTEM_PROMPT, ASYNC_MARKER};
use crate::llm::{
ASYNC_MARKER, ChatResult, Conversation, ConversationConfig, DEFAULT_SYSTEM_PROMPT, ToolExecutor,
};
use crate::queue::{ConsumerChannels, EnqueueHandle, MessageKind, PipelineMessage, QueueRunner};
use crate::state::StateManager;
use crate::tools::approval::{ApprovalDecision, ApprovalManager};
use crate::wechat::client::WeChatClient;
use std::collections::HashMap;
@@ -59,8 +61,8 @@ use uuid::Uuid;
/// 这个结构体被所有三个消费者(LLM / Tool / Send)通过 `Arc` 共享访问。
/// 每个字段都只读(或内部有锁),所以不需要额外的 `Mutex` 包装。
struct DaemonCtx {
/// 数据库句柄(可选,没有时走文件存储)
db: Option<Arc<Database>>,
/// 数据库句柄
db: Arc<Database>,
/// WeChat API 客户端(内部用 Arc<Mutex> 保护 token/updates_buf
client: WeChatClient,
/// 本机器人的微信账号 ID
@@ -83,33 +85,36 @@ struct DaemonCtx {
/// Daemon 入口
///
/// `sock_path` 保留用于兼容旧版 daemon 命令的 CLI 接口,实际已不再使用。
pub async fn run(
_sock_path: String,
database: &Option<Arc<Database>>,
file_state: &StateManager,
memory_store: &Arc<MemoryStore>,
) {
pub async fn run(database: &Arc<Database>, memory_store: &Arc<MemoryStore>) {
// 1-3. 认证 & 客户端
let auth = match load_auth(database, file_state).await {
let auth = match load_auth(database).await {
Some(a) => a,
None => { error!("未登录"); return; }
None => {
error!("未登录");
return;
}
};
let mut client = WeChatClient::new(Some(auth.base_url.clone()));
client.set_auth(&auth.token, &auth.account_id, &auth.base_url).await;
if let Some(r) = file_state.load_runtime() { client.set_updates_buf(&r.get_updates_buf).await; }
if let Err(e) = client.notify_start().await { error!("注册监听器失败: {}", e); return; }
client
.set_auth(&auth.token, &auth.account_id, &auth.base_url)
.await;
if let Some(buf) = crate::db::models::load_runtime(database.pool()).await {
client.set_updates_buf(&buf).await;
}
if let Err(e) = client.notify_start().await {
error!("注册监听器失败: {}", e);
return;
}
let account_id = auth.account_id.clone();
info!("Daemon 已启动");
// 4-6. 审批管理器、工具列表、模型配置
let approval_manager = Arc::new(ApprovalManager::new(
database.as_ref().map(|db| Arc::new(db.pool().clone())),
Some(Arc::new(database.pool().clone())),
));
let tools_list = build_tools_list();
let system_prompt =
std::env::var("WEIXIN_LLM_SYSTEM_PROMPT").unwrap_or_else(|_| String::new());
let model =
std::env::var("DEEPSEEK_MODEL").unwrap_or_else(|_| "deepseek-v4-flash".to_string());
let system_prompt = std::env::var("WEIXIN_LLM_SYSTEM_PROMPT").unwrap_or_else(|_| String::new());
let model = std::env::var("DEEPSEEK_MODEL").unwrap_or_else(|_| "deepseek-v4-flash".to_string());
// 7. Daemon 上下文(注意 approval_ctx 为 5 元组)
let ctx = Arc::new(DaemonCtx {
@@ -132,7 +137,10 @@ pub async fn run(
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
approval_clean.clean_expired().await;
let now = Instant::now();
ctx_for_clean.approval_ctx.lock().await
ctx_for_clean
.approval_ctx
.lock()
.await
.retain(|_, (_, _, _, _, created)| now.duration_since(*created).as_secs() < 300);
}
});
@@ -140,32 +148,43 @@ pub async fn run(
// 9. 队列(容量 1024
let (runner, channels) = QueueRunner::new(1024);
let enqueue_handle = runner.enqueue_handle();
let shutdown_handle = runner.shutdown_handle(); // 不再 prefix _
let shutdown_handle = runner.shutdown_handle(); // 不再 prefix _
// 10-11. 消费者 + 调度器
let (consumer_handles, sessions) = spawn_consumers(channels, ctx.clone(), enqueue_handle.clone());
let (consumer_handles, sessions) =
spawn_consumers(channels, ctx.clone(), enqueue_handle.clone());
if let Some(sched_db) = database.as_ref().map(|db| db.pool().clone()) {
let sched_enqueue = enqueue_handle.clone();
tokio::spawn(async move {
let scheduler = crate::scheduler::Scheduler::new(Some(Arc::new(sched_db)));
scheduler.run(move |to: &str, name: &str, msg: &str| {
let eq = sched_enqueue.clone();
let uid = to.to_string(); let text = msg.to_string(); let tn = name.to_string();
tokio::spawn(async move {
eq.enqueue(PipelineMessage {
channel: ChannelId::wechat(uid), correlation_id: Uuid::new_v4(),
kind: MessageKind::ScheduledTask { text, task_name: tn },
}).await;
});
Ok(())
}).await;
let sched_db = database.pool().clone();
let sched_enqueue = enqueue_handle.clone();
tokio::spawn(async move {
let scheduler = crate::scheduler::Scheduler::new(Some(Arc::new(sched_db)));
scheduler
.run(move |to: &str, name: &str, msg: &str| {
let eq = sched_enqueue.clone();
let uid = to.to_string();
let text = msg.to_string();
let tn = name.to_string();
tokio::spawn(async move {
eq.enqueue(PipelineMessage {
channel: ChannelId::wechat(uid),
correlation_id: Uuid::new_v4(),
kind: MessageKind::ScheduledTask {
text,
task_name: tn,
},
})
.await;
});
Ok(())
})
.await;
});
info!("定时任务调度器已启动");
}
// 12. QueueRunner
let runner_handle = tokio::spawn(async move { runner.run().await; });
let runner_handle = tokio::spawn(async move {
runner.run().await;
});
// 13. Session TTL 清理(5 分钟无活动)
let sessions_for_clean = sessions.clone();
@@ -173,7 +192,9 @@ pub async fn run(
loop {
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
let now = Instant::now();
sessions_for_clean.lock().await
sessions_for_clean
.lock()
.await
.retain(|_, (_, created)| now.duration_since(*created).as_secs() < 300);
}
});
@@ -188,7 +209,9 @@ pub async fn run(
match msg_result {
Ok(resp) => {
if !resp.get_updates_buf.is_empty() {
file_state.save_runtime(&resp.get_updates_buf);
let _ = crate::db::models::save_runtime(
ctx.db.pool(), &resp.get_updates_buf,
).await;
}
for msg in &resp.msgs {
if msg.msg_type != Some(crate::wechat::types::WeixinMessage::TYPE_USER) { continue; }
@@ -198,11 +221,9 @@ pub async fn run(
let msg_id = msg.message_id.map(|id| id.to_string()).unwrap_or_default();
info!("收到消息 from={}: {}", from, text);
if let Some(db) = &ctx.db {
let _ = crate::db::models::insert_chat_record(
db.pool(), "inbound", from, &ctx.account_id, text, "wechat", ctx_token, &msg_id,
let _ = crate::db::models::insert_chat_record(
ctx.db.pool(), "inbound", from, &ctx.account_id, text, "wechat", ctx_token, &msg_id,
).await;
}
// 审批回复
if let Some((skill_name, decision)) = ctx.approval.handle_reply(from, text).await {
@@ -256,7 +277,9 @@ pub async fn run(
shutdown_handle.shutdown();
info!("等待消费者处理剩余消息...");
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
for h in consumer_handles { h.abort(); }
for h in consumer_handles {
h.abort();
}
runner_handle.abort();
info!("Daemon 已停止");
}
@@ -267,8 +290,12 @@ fn spawn_consumers(
channels: ConsumerChannels,
ctx: Arc<DaemonCtx>,
enqueue: EnqueueHandle,
) -> (Vec<tokio::task::JoinHandle<()>>, Arc<Mutex<HashMap<Uuid, (Conversation, Instant)>>>) {
let sessions: Arc<Mutex<HashMap<Uuid, (Conversation, Instant)>>> = Arc::new(Mutex::new(HashMap::new()));
) -> (
Vec<tokio::task::JoinHandle<()>>,
Arc<Mutex<HashMap<Uuid, (Conversation, Instant)>>>,
) {
let sessions: Arc<Mutex<HashMap<Uuid, (Conversation, Instant)>>> =
Arc::new(Mutex::new(HashMap::new()));
let mut handles = Vec::new();
// LLM Consumer
@@ -277,20 +304,26 @@ fn spawn_consumers(
let ctx = ctx.clone();
let eq = enqueue.clone();
let s = sessions.clone();
handles.push(tokio::spawn(async move { llm_consumer_loop(&mut rx, &ctx, &eq, &s).await; }));
handles.push(tokio::spawn(async move {
llm_consumer_loop(&mut rx, &ctx, &eq, &s).await;
}));
}
// Tool Consumer
{
let mut rx = channels.tool_rx;
let ctx = ctx.clone();
let eq = enqueue.clone();
handles.push(tokio::spawn(async move { tool_consumer_loop(&mut rx, &ctx, &eq).await; }));
handles.push(tokio::spawn(async move {
tool_consumer_loop(&mut rx, &ctx, &eq).await;
}));
}
// Send Consumer
{
let mut rx = channels.send_rx;
let ctx = ctx.clone();
handles.push(tokio::spawn(async move { send_consumer_loop(&mut rx, &ctx).await; }));
handles.push(tokio::spawn(async move {
send_consumer_loop(&mut rx, &ctx).await;
}));
}
(handles, sessions)
}
@@ -310,7 +343,12 @@ async fn llm_consumer_loop(
let correlation_id = msg.correlation_id;
match msg.kind {
MessageKind::UserMessage { text, message_id: _, context_token, approved_tool } => {
MessageKind::UserMessage {
text,
message_id: _,
context_token,
approved_tool,
} => {
info!("LLM Consumer: user={} text={:.60}", user_id, text);
// 加载数据
@@ -319,7 +357,9 @@ async fn llm_consumer_loop(
let sys_prompt = if ctx.system_prompt.is_empty() {
DEFAULT_SYSTEM_PROMPT.to_string()
} else { ctx.system_prompt.clone() };
} else {
ctx.system_prompt.clone()
};
let cfg = ConversationConfig {
system_prompt: sys_prompt,
@@ -330,12 +370,13 @@ async fn llm_consumer_loop(
let mut conv = match Conversation::new(cfg) {
Ok(c) => c,
Err(e) => { error!("LLM 初始化失败: {}", e); continue; }
Err(e) => {
error!("LLM 初始化失败: {}", e);
continue;
}
};
if let Some(db) = &ctx.db {
conv.session().lock().await.db_pool = Some(Arc::new(db.pool().clone()));
}
conv.session().lock().await.db_pool = Some(Arc::new(ctx.db.pool().clone()));
{
let s_arc = conv.session();
@@ -345,7 +386,10 @@ async fn llm_consumer_loop(
}
if let Some(tool) = &approved_tool {
conv.session().lock().await.add_system_note(&format!("\n[用户已批准工具: {}]", tool));
conv.session()
.lock()
.await
.add_system_note(&format!("\n[用户已批准工具: {}]", tool));
}
// 设置 ToolExecutor — 所有工具统一通过消息队列异步执行
@@ -355,7 +399,7 @@ async fn llm_consumer_loop(
let session = conv.session();
let approved_tool = approved_tool.clone();
let executor: ToolExecutor = Arc::new(move |name: &str, args_json: &str| {
let executor: ToolExecutor = Arc::new(move |name: &str, args_json: &str, tc_id: &str| {
let eq = eq.clone();
let uid = uid.clone();
let corr = corr;
@@ -363,19 +407,33 @@ async fn llm_consumer_loop(
let n = name.to_string();
let args = args_json.to_string();
let is_approved = approved_tool.as_ref() == Some(&n);
let tool_call_id = tc_id.to_string();
Box::pin(async move {
let ch = ChannelId::wechat(&uid);
let sid = { session.lock().await.session_id };
let tool_call_id = Uuid::new_v4().to_string();
if is_approved {
eq.enqueue(PipelineMessage::approved_tool_call(
ch, corr, sid, &tool_call_id, &n, &args, None,
)).await;
ch,
corr,
sid,
&tool_call_id,
&n,
&args,
None,
))
.await;
} else {
eq.enqueue(PipelineMessage::tool_call(
ch, corr, sid, &tool_call_id, &n, &args, None,
)).await;
ch,
corr,
sid,
&tool_call_id,
&n,
&args,
None,
))
.await;
}
Ok(format!("{}{}", ASYNC_MARKER, n))
})
@@ -383,30 +441,55 @@ async fn llm_consumer_loop(
conv.set_tool_executor(executor);
// 缓存 Conversation(记录创建时间用于 TTL
sessions.lock().await.insert(correlation_id, (conv, Instant::now()));
sessions
.lock()
.await
.insert(correlation_id, (conv, Instant::now()));
// 执行 LLM 对话
run_llm_round(&user_id, correlation_id, context_token, &text, sessions, enqueue).await;
run_llm_round(
&user_id,
correlation_id,
context_token,
&text,
sessions,
enqueue,
)
.await;
}
MessageKind::ToolResult { session_id: _, tool_call_id, tool_name, result, context_token } => {
info!("LLM Consumer: 工具结果回喂 user={} tool={}", user_id, tool_name);
MessageKind::ToolResult {
session_id: _,
tool_call_id,
tool_name,
result,
context_token,
} => {
info!(
"LLM Consumer: 工具结果回喂 user={} tool={}",
user_id, tool_name
);
// 从缓存恢复会话,注入工具结果并恢复 LLM 循环
let should_continue = {
let should_resume = {
let mut map = sessions.lock().await;
if let Some((conv, last_access)) = map.get_mut(&correlation_id) {
conv.session().lock().await.add_tool_result(&tool_call_id, &tool_name, &result);
conv.session().lock().await.add_tool_result(
&tool_call_id,
&tool_name,
&result,
);
*last_access = Instant::now(); // 更新访问时间,防止 TTL 误清理
true
conv.notify_tool_result()
} else {
warn!("会话已过期: corr={}", correlation_id);
false
}
};
if should_continue {
resume_llm_round(&user_id, correlation_id, context_token, sessions, enqueue).await;
if should_resume {
resume_llm_round(&user_id, correlation_id, context_token, sessions, enqueue)
.await;
}
}
@@ -431,7 +514,10 @@ async fn llm_consumer_loop(
eq.enqueue(msg).await;
}
other => warn!("LLM Consumer 收到不期望的消息: {:?}", std::mem::discriminant(&other)),
other => warn!(
"LLM Consumer 收到不期望的消息: {:?}",
std::mem::discriminant(&other)
),
}
}
info!("LLM Consumer 已停止");
@@ -450,7 +536,10 @@ async fn run_llm_round(
let map = sessions.lock().await;
let conv = match map.get(&correlation_id) {
Some((c, _)) => c,
None => { error!("run_llm_round: conv 不在缓存"); return; }
None => {
error!("run_llm_round: conv 不在缓存");
return;
}
};
match conv.chat_with_tools(text.to_string()).await {
@@ -467,7 +556,15 @@ async fn run_llm_round(
}
};
handle_chat_result(user_id, correlation_id, context_token, result, sessions, enqueue).await;
handle_chat_result(
user_id,
correlation_id,
context_token,
result,
sessions,
enqueue,
)
.await;
}
/// 在异步工具结果返回后恢复 LLM 循环(不追加用户消息)。
@@ -482,7 +579,10 @@ async fn resume_llm_round(
let map = sessions.lock().await;
let conv = match map.get(&correlation_id) {
Some((c, _)) => c,
None => { error!("resume_llm_round: conv 不在缓存"); return; }
None => {
error!("resume_llm_round: conv 不在缓存");
return;
}
};
match conv.resume_tool_loop().await {
@@ -499,7 +599,15 @@ async fn resume_llm_round(
}
};
handle_chat_result(user_id, correlation_id, context_token, result, sessions, enqueue).await;
handle_chat_result(
user_id,
correlation_id,
context_token,
result,
sessions,
enqueue,
)
.await;
}
/// 统一处理 ChatResult:有异步等待则保留 session,否则发送回复并清理。
@@ -544,7 +652,14 @@ async fn tool_consumer_loop(
let correlation_id = msg.correlation_id;
match msg.kind {
MessageKind::ToolCall { session_id, tool_call_id, tool_name, arguments, context_token, approved } => {
MessageKind::ToolCall {
session_id,
tool_call_id,
tool_name,
arguments,
context_token,
approved,
} => {
info!("Tool Consumer: user={} tool={}", user_id, tool_name);
// 高风险工具 → 审批(除非已被用户批准)
@@ -568,7 +683,9 @@ async fn tool_consumer_loop(
);
// 通过消息队列发送审批提示(不直接调 send_text)
let ch = ChannelId::wechat(&user_id);
enqueue.enqueue(PipelineMessage::llm_reply(ch, correlation_id, &m, None)).await;
enqueue
.enqueue(PipelineMessage::llm_reply(ch, correlation_id, &m, None))
.await;
}
Err(e) => error!("创建审批失败: {}", e),
}
@@ -578,14 +695,25 @@ async fn tool_consumer_loop(
// 执行工具 — 所有工具统一在此处理
let output = execute_tool(&tool_name, &arguments, ctx, &user_id).await;
enqueue.enqueue(PipelineMessage::tool_result(
ChannelId::wechat(&user_id), correlation_id, session_id, &tool_call_id, &tool_name, &output, context_token,
)).await;
enqueue
.enqueue(PipelineMessage::tool_result(
ChannelId::wechat(&user_id),
correlation_id,
session_id,
&tool_call_id,
&tool_name,
&output,
context_token,
))
.await;
}
MessageKind::ApprovalRequest { .. } => {
warn!("Tool Consumer 收到 ApprovalRequest");
}
other => warn!("Tool Consumer 收到不期望: {:?}", std::mem::discriminant(&other)),
other => warn!(
"Tool Consumer 收到不期望: {:?}",
std::mem::discriminant(&other)
),
}
}
info!("Tool Consumer 已停止");
@@ -611,30 +739,52 @@ fn execute_tool<'a>(
"write_memory" => {
let params: serde_json::Value = serde_json::from_str(arguments).unwrap_or_default();
let content = params.get("content").and_then(|v| v.as_str()).unwrap_or("");
if content.is_empty() { return "请提供 content 参数".to_string(); }
if content.is_empty() {
return "请提供 content 参数".to_string();
}
ctx.memory_store.write_for(user_id, content).await;
return format!("已添加长期记忆: {}", content);
}
"delete_memory" => {
let params: serde_json::Value = serde_json::from_str(arguments).unwrap_or_default();
let memory_id = params.get("memory_id").and_then(|v| v.as_i64()).unwrap_or(-1) as i32;
if memory_id <= 0 { return "请提供有效的 memory_id 参数".to_string(); }
let memory_id = params
.get("memory_id")
.and_then(|v| v.as_i64())
.unwrap_or(-1) as i32;
if memory_id <= 0 {
return "请提供有效的 memory_id 参数".to_string();
}
return ctx.memory_store.delete_for(user_id, memory_id).await;
}
"update_memory" => {
let params: serde_json::Value = serde_json::from_str(arguments).unwrap_or_default();
let memory_id = params.get("memory_id").and_then(|v| v.as_i64()).unwrap_or(-1) as i32;
let memory_id = params
.get("memory_id")
.and_then(|v| v.as_i64())
.unwrap_or(-1) as i32;
let content = params.get("content").and_then(|v| v.as_str()).unwrap_or("");
if memory_id <= 0 { return "请提供有效的 memory_id 参数".to_string(); }
if content.is_empty() { return "请提供 content 参数".to_string(); }
return ctx.memory_store.update_for(user_id, memory_id, content).await;
if memory_id <= 0 {
return "请提供有效的 memory_id 参数".to_string();
}
if content.is_empty() {
return "请提供 content 参数".to_string();
}
return ctx
.memory_store
.update_for(user_id, memory_id, content)
.await;
}
"read_summaries" => {
let sums = load_summaries(&ctx.db, user_id).await;
if sums.is_empty() { return "暂无历史摘要".to_string(); }
return sums.iter().enumerate()
if sums.is_empty() {
return "暂无历史摘要".to_string();
}
return sums
.iter()
.enumerate()
.map(|(i, s)| format!("{}. {}", i + 1, s))
.collect::<Vec<_>>().join("\n---\n");
.collect::<Vec<_>>()
.join("\n---\n");
}
"query_capabilities" => {
let specs = crate::tools::specs();
@@ -643,7 +793,11 @@ fn execute_tool<'a>(
"call_capability" => {
// 解包 {name, prompt} → 提取目标工具名和参数,递归派发
let cp: serde_json::Value = serde_json::from_str(arguments).unwrap_or_default();
let target_name = cp.get("name").and_then(|v| v.as_str()).unwrap_or("").to_string();
let target_name = cp
.get("name")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
if target_name.is_empty() {
return "请提供 name 参数".to_string();
}
@@ -677,24 +831,39 @@ async fn send_consumer_loop(
while let Some(msg) = rx.recv().await {
match msg.kind {
MessageKind::LLMReply { text, context_token } => {
MessageKind::LLMReply {
text,
context_token,
} => {
let user_id = &msg.channel.user_id;
info!("Send Consumer: user={} text_len={}", user_id, text.len());
match ctx.client.send_text(user_id, &text, context_token.as_deref()).await {
match ctx
.client
.send_text(user_id, &text, context_token.as_deref())
.await
{
Ok(sent_id) => {
info!("回复成功 user={} msg_id={}", user_id, sent_id);
if let Some(db) = &ctx.db {
let _ = crate::db::models::insert_chat_record(
db.pool(), "outbound", user_id, &ctx.account_id,
&text, "llm", context_token.as_deref(), &sent_id,
).await;
}
let _ = crate::db::models::insert_chat_record(
ctx.db.pool(),
"outbound",
user_id,
&ctx.account_id,
&text,
"llm",
context_token.as_deref(),
&sent_id,
)
.await;
}
Err(e) => error!("发送回复失败: {}", e),
}
}
other => warn!("Send Consumer 收到不期望: {:?}", std::mem::discriminant(&other)),
other => warn!(
"Send Consumer 收到不期望: {:?}",
std::mem::discriminant(&other)
),
}
}
info!("Send Consumer 已停止");
@@ -702,37 +871,38 @@ async fn send_consumer_loop(
// ─── 辅助函数 ───
async fn load_auth(
database: &Option<Arc<Database>>,
file_state: &StateManager,
) -> Option<crate::state::AuthState> {
if let Some(db) = database {
if let Some(a) = crate::db::models::load_auth(db.pool()).await { return Some(a); }
if let Some(fa) = file_state.load_auth() {
let a = crate::state::AuthState { token: fa.token, account_id: fa.account_id, base_url: fa.base_url };
if let Err(e) = crate::db::models::save_auth(db.pool(), &a).await { error!("保存 auth 失败: {}", e); }
return Some(a);
}
} else if let Some(a) = file_state.load_auth() { return Some(a); }
None
async fn load_auth(database: &Arc<Database>) -> Option<crate::db::models::AuthState> {
crate::db::models::load_auth(database.pool()).await
}
async fn load_history(db: &Option<Arc<Database>>, user_id: &str) -> Vec<HistoryEntry> {
let Some(db) = db else { return vec![]; };
async fn load_history(db: &Arc<Database>, user_id: &str) -> Vec<HistoryEntry> {
match crate::db::models::list_recent_chat_records(db.pool(), user_id, 20).await {
Ok(records) => records.into_iter().rev().map(|r| HistoryEntry {
role: if r.direction == "inbound" { "user".into() } else { "assistant".into() },
content: r.text,
}).collect(),
Err(e) => { warn!("加载历史失败: {}", e); vec![] }
Ok(records) => records
.into_iter()
.rev()
.map(|r| HistoryEntry {
role: if r.direction == "inbound" {
"user".into()
} else {
"assistant".into()
},
content: r.text,
})
.collect(),
Err(e) => {
warn!("加载历史失败: {}", e);
vec![]
}
}
}
async fn load_summaries(db: &Option<Arc<Database>>, user_id: &str) -> Vec<String> {
let Some(db) = db else { return vec![]; };
async fn load_summaries(db: &Arc<Database>, user_id: &str) -> Vec<String> {
match crate::db::models::load_summaries(db.pool(), user_id, 5).await {
Ok(s) => s,
Err(e) => { warn!("加载摘要失败: {}", e); vec![] }
Err(e) => {
warn!("加载摘要失败: {}", e);
vec![]
}
}
}