Files
iAs/src/daemon.rs
T
wunianxiao e20cd19e57 chore: 增强日志追踪与代码格式化
- daemon: 添加 LLM 回合开始/结束标记日志,工具执行结果追踪
- daemon: 发送消费者日志增强(显示完整文本、截断用户 ID)
- conversation: 移除日志截断,输出完整工具调用参数和结果
- deepseek: 代码格式化,预留请求/响应摘要日志
- 删除 systemd/ias.service
2026-06-16 22:53:19 +08:00

1029 lines
38 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.
//! ## Daemon —— 常驻守护进程(核心循环)
//!
//! 这是整个项目的核心入口循环。它的职责:
//!
//! 1. **持有 WeChat 长连接** — 通过 `WeChatClient` 长轮询接收微信消息
//! 2. **持有数据库连接** — 可选 PostgreSQL,回退到文件存储
//! 3. **管理审批流程** — `ApprovalManager` 处理高风险工具的确认码审批
//! 4. **管理长期记忆** — `MemoryStore` 提供按用户隔离的记忆读写
//!
//! ### 消息处理架构(三消费者 + 公平队列)
//!
//! daemon 将消息处理拆分为 3 个独立的 tokio async task(消费者),
//! 通过 `MessageQueue` + `QueueRunner` 做公平轮转路由:
//!
//! ```text
//! receive_messages → enqueue PipelineMessage
//! │
//! ▼
//! ┌──────────────────┐
//! │ MessageQueue │ ← 公平轮转(A1, B1, A2, A3
//! └────────┬─────────┘
//! │ dequeue
//! ▼
//! ┌──────────────────┐
//! │ QueueRunner │ ← 按 MessageKind 路由
//! └──┬──────┬──────┬─┘
//! │ │ │
//! ▼ ▼ ▼
//! LLM Tool Send
//! Cons. Cons. Cons.
//! ```
//!
//! - **LLM Consumer** — 调用 DeepSeek API 做对话 + 工具循环
//! - **Tool Consumer** — 执行内置工具,高风险工具先走审批
//! - **Send Consumer** — 将 LLM 回复通过 WeChat API 发回给用户
//!
//! ### 架构
//!
//! 采用统一的单进程 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::llm::{
ASYNC_MARKER, ChatResult, Conversation, ConversationConfig, DEFAULT_SYSTEM_PROMPT, ToolExecutor,
};
use crate::queue::{ConsumerChannels, EnqueueHandle, MessageKind, PipelineMessage, QueueRunner};
use crate::tools::approval::{ApprovalDecision, ApprovalManager};
use crate::wechat::client::WeChatClient;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Instant;
use tokio::sync::Mutex;
use tracing::{error, info, warn};
use uuid::Uuid;
/// ## Daemon 上下文 —— 消费者之间共享的全部状态
///
/// 这个结构体被所有三个消费者(LLM / Tool / Send)通过 `Arc` 共享访问。
/// 每个字段都只读(或内部有锁),所以不需要额外的 `Mutex` 包装。
struct DaemonCtx {
/// 数据库句柄
db: Arc<Database>,
/// WeChat API 客户端(内部用 Arc<Mutex> 保护 token/updates_buf
client: WeChatClient,
/// 本机器人的微信账号 ID
account_id: String,
/// 审批管理器 —— 处理高风险工具的用户确认码流程
approval: Arc<ApprovalManager>,
/// 长期记忆管理器 —— 按用户隔离的记忆读写
memory_store: Arc<MemoryStore>,
/// 注册给 LLM 的工具列表(JSON 格式,符合 function calling 规范)
tools_list: Vec<serde_json::Value>,
/// 系统提示词,覆盖默认值
system_prompt: String,
/// 使用的模型名称(如 `deepseek-v4-flash`
model: String,
/// 待审批的上下文:`user_id → (原始文本, context_token, 工具名, 工具参数, 创建时间)`
/// 用户在聊天中发送确认码时,从这里面查找匹配项
approval_ctx: Mutex<HashMap<String, (String, Option<String>, String, String, Instant)>>,
}
/// Daemon 入口
///
/// `sock_path` 保留用于兼容旧版 daemon 命令的 CLI 接口,实际已不再使用。
pub async fn run(database: &Arc<Database>, memory_store: &Arc<MemoryStore>) {
// 1-3. 认证 & 客户端
let auth = match load_auth(database).await {
Some(a) => a,
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(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(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());
// 7. Daemon 上下文(注意 approval_ctx 为 5 元组)
let ctx = Arc::new(DaemonCtx {
db: database.clone(),
client: client.clone(),
account_id: account_id.clone(),
approval: approval_manager.clone(),
memory_store: memory_store.clone(),
tools_list: tools_list.clone(),
system_prompt,
model,
approval_ctx: Mutex::new(HashMap::new()),
});
// 8. 审批过期清理
let ctx_for_clean = ctx.clone();
let approval_clean = approval_manager.clone();
tokio::spawn(async move {
loop {
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
.retain(|_, (_, _, _, _, created)| now.duration_since(*created).as_secs() < 300);
}
});
// 9. 队列(容量 1024
let (runner, channels) = QueueRunner::new(1024);
let enqueue_handle = runner.enqueue_handle();
let shutdown_handle = runner.shutdown_handle(); // 不再 prefix _
// 10-11. 消费者 + 调度器
let (consumer_handles, sessions) =
spawn_consumers(channels, ctx.clone(), enqueue_handle.clone());
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;
});
// 13. Session TTL 清理(5 分钟无活动)
let sessions_for_clean = sessions.clone();
tokio::spawn(async move {
loop {
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
let now = Instant::now();
sessions_for_clean
.lock()
.await
.retain(|_, (_, created)| now.duration_since(*created).as_secs() < 300);
}
});
info!("开始监听消息...");
// 13. 主循环 — 带 Ctrl+C 优雅退出
let mut running = true;
while running {
tokio::select! {
msg_result = client.receive_messages() => {
match msg_result {
Ok(resp) => {
if !resp.get_updates_buf.is_empty() {
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; }
let from = msg.from_user_id.as_deref().unwrap_or("unknown");
let text = msg.text_content().unwrap_or("(非文本消息)");
let ctx_token = msg.context_token.as_deref();
let msg_id = msg.message_id.map(|id| id.to_string()).unwrap_or_default();
info!("收到消息 from={}: {}", from, text);
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 {
match decision {
ApprovalDecision::Approved => {
info!("审批通过: {}", skill_name);
// 5 元组: (orig_text, context_token, tool_name, tool_args, timestamp)
if let Some((orig_text, orig_ctx, _tn, _ta, _ts)) = ctx.approval_ctx.lock().await.remove(from) {
enqueue_handle.enqueue(PipelineMessage {
channel: ChannelId::wechat(from),
correlation_id: Uuid::new_v4(),
kind: MessageKind::UserMessage {
text: orig_text, message_id: String::new(),
context_token: orig_ctx, approved_tool: Some(skill_name),
},
}).await;
}
}
ApprovalDecision::Rejected | ApprovalDecision::Expired => {
ctx.approval_ctx.lock().await.remove(from);
}
}
continue;
}
enqueue_handle.enqueue(PipelineMessage {
channel: ChannelId::wechat(from),
correlation_id: Uuid::new_v4(),
kind: MessageKind::UserMessage {
text: text.to_string(), message_id: msg_id,
context_token: ctx_token.map(String::from), approved_tool: None,
},
}).await;
}
}
Err(e) => {
if !e.contains("超时") && !e.contains("timeout") {
error!("接收消息失败: {}", e);
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
}
}
}
}
_ = tokio::signal::ctrl_c() => {
info!("收到 Ctrl+C,优雅关闭中...");
running = false;
}
}
}
shutdown_handle.shutdown();
info!("等待消费者处理剩余消息...");
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
for h in consumer_handles {
h.abort();
}
runner_handle.abort();
info!("Daemon 已停止");
}
// ─── 消费者 ───
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()));
let mut handles = Vec::new();
// LLM Consumer
{
let mut rx = channels.llm_rx;
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;
}));
}
// 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;
}));
}
// 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, sessions)
}
// ─── LLM Consumer ───
async fn llm_consumer_loop(
rx: &mut tokio::sync::mpsc::Receiver<PipelineMessage>,
ctx: &DaemonCtx,
enqueue: &EnqueueHandle,
sessions: &Arc<Mutex<HashMap<Uuid, (Conversation, Instant)>>>,
) {
info!("LLM Consumer 已启动");
while let Some(msg) = rx.recv().await {
let user_id = msg.channel.user_id.clone();
let correlation_id = msg.correlation_id;
match msg.kind {
MessageKind::UserMessage {
text,
message_id: _,
context_token,
approved_tool,
} => {
// 加载数据
ctx.memory_store.load_if_needed(&user_id).await;
let history = load_history(&ctx.db, &user_id).await;
let sys_prompt = if ctx.system_prompt.is_empty() {
DEFAULT_SYSTEM_PROMPT.to_string()
} else {
ctx.system_prompt.clone()
};
let cfg = ConversationConfig {
system_prompt: sys_prompt,
model: ctx.model.clone(),
tools: Some(ctx.tools_list.clone()),
..Default::default()
};
let mut conv = match Conversation::new(cfg) {
Ok(c) => c,
Err(e) => {
error!("LLM 初始化失败: {}", e);
continue;
}
};
conv.session().lock().await.db_pool = Some(Arc::new(ctx.db.pool().clone()));
{
let s_arc = conv.session();
let mut s = s_arc.lock().await;
s.load_from_history(&history);
s.current_user_id = user_id.clone();
}
if let Some(tool) = &approved_tool {
conv.session()
.lock()
.await
.add_system_note(&format!("\n[用户已批准工具: {}]", tool));
}
// 设置 ToolExecutor — 所有工具统一通过消息队列异步执行
let eq = enqueue.clone();
let uid = user_id.clone();
let corr = correlation_id;
let session = conv.session();
let approved_tool = approved_tool.clone();
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;
let session = session.clone();
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 };
if is_approved {
eq.enqueue(PipelineMessage::approved_tool_call(
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;
}
Ok(format!("{}{}", ASYNC_MARKER, n))
})
});
conv.set_tool_executor(executor);
// 缓存 Conversation(记录创建时间用于 TTL
sessions
.lock()
.await
.insert(correlation_id, (conv, Instant::now()));
// 执行 LLM 对话
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,
} => {
// 从缓存恢复会话,注入工具结果并恢复 LLM 循环
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,
);
*last_access = Instant::now(); // 更新访问时间,防止 TTL 误清理
conv.notify_tool_result()
} else {
warn!("会话已过期: corr={}", correlation_id);
false
}
};
if should_resume {
resume_llm_round(&user_id, correlation_id, context_token, sessions, enqueue)
.await;
}
}
MessageKind::ScheduledTask { text, task_name } => {
info!("LLM Consumer: 定时任务 user={} task={}", user_id, task_name);
let prompt = format!("[定时任务: {}]\n{}", task_name, text);
// 为保持简洁,定时任务直接作为用户消息处理(使用全新 correlation_id
let msg = PipelineMessage {
channel: ChannelId::wechat(user_id.clone()),
correlation_id: Uuid::new_v4(),
kind: MessageKind::UserMessage {
text: prompt,
message_id: String::new(),
context_token: None,
approved_tool: None,
},
};
// 重新投递给自己 — 简单但可靠
// 注意: 这里用了一个技巧,重新入队而不是复制代码
let eq = enqueue.clone();
eq.enqueue(msg).await;
}
other => warn!(
"LLM Consumer 收到不期望的消息: {:?}",
std::mem::discriminant(&other)
),
}
}
info!("LLM Consumer 已停止");
}
/// 执行一轮 LLM 对话,仅在无异步工具等待时发送回复并清理会话。
async fn run_llm_round(
user_id: &str,
correlation_id: Uuid,
context_token: Option<String>,
text: &str,
sessions: &Arc<Mutex<HashMap<Uuid, (Conversation, Instant)>>>,
enqueue: &EnqueueHandle,
) {
info!("══════════════════════════════════════════════════");
info!("🔄 LLM 回合开始 user={} text={}", user_id, text);
let result = {
let map = sessions.lock().await;
let conv = match map.get(&correlation_id) {
Some((c, _)) => c,
None => {
error!("run_llm_round: conv 不在缓存");
return;
}
};
match conv.chat_with_tools(text.to_string()).await {
Ok(r) => r,
Err(e) => {
error!("LLM 对话失败: {}", e);
ChatResult {
reply: format!("处理失败: {}", e),
used_tools: false,
usage: None,
has_pending_async: false,
}
}
}
};
handle_chat_result(
user_id,
correlation_id,
context_token,
result,
sessions,
enqueue,
)
.await;
}
/// 在异步工具结果返回后恢复 LLM 循环(不追加用户消息)。
async fn resume_llm_round(
user_id: &str,
correlation_id: Uuid,
context_token: Option<String>,
sessions: &Arc<Mutex<HashMap<Uuid, (Conversation, Instant)>>>,
enqueue: &EnqueueHandle,
) {
info!("══════════════════════════════════════════════════");
info!("🔄 LLM 恢复 user={}", user_id);
let result = {
let map = sessions.lock().await;
let conv = match map.get(&correlation_id) {
Some((c, _)) => c,
None => {
error!("resume_llm_round: conv 不在缓存");
return;
}
};
match conv.resume_tool_loop().await {
Ok(r) => r,
Err(e) => {
error!("LLM 恢复失败: {}", e);
ChatResult {
reply: format!("处理失败: {}", e),
used_tools: false,
usage: None,
has_pending_async: false,
}
}
}
};
handle_chat_result(
user_id,
correlation_id,
context_token,
result,
sessions,
enqueue,
)
.await;
}
/// 统一处理 ChatResult:有异步等待则保留 session,否则发送回复并清理。
async fn handle_chat_result(
user_id: &str,
correlation_id: Uuid,
context_token: Option<String>,
result: ChatResult,
sessions: &Arc<Mutex<HashMap<Uuid, (Conversation, Instant)>>>,
enqueue: &EnqueueHandle,
) {
if result.has_pending_async {
// 有异步工具等待返回 → 保留 session
if !result.reply.is_empty() {
let ch = ChannelId::wechat(user_id);
let msg = PipelineMessage::llm_reply(ch, correlation_id, &result.reply, context_token);
enqueue.enqueue(msg).await;
}
return;
}
// 对话完成:发送回复并清理 session
if !result.reply.is_empty() {
let ch = ChannelId::wechat(user_id);
let msg = PipelineMessage::llm_reply(ch, correlation_id, &result.reply, context_token);
enqueue.enqueue(msg).await;
}
sessions.lock().await.remove(&correlation_id);
info!("✅ LLM 回合结束 user={}", user_id);
}
// ─── Tool Consumer ───
async fn tool_consumer_loop(
rx: &mut tokio::sync::mpsc::Receiver<PipelineMessage>,
ctx: &DaemonCtx,
enqueue: &EnqueueHandle,
) {
info!("Tool Consumer 已启动");
while let Some(msg) = rx.recv().await {
let user_id = msg.channel.user_id.clone();
let correlation_id = msg.correlation_id;
match msg.kind {
MessageKind::ToolCall {
session_id,
tool_call_id,
tool_name,
arguments,
context_token,
approved,
} => {
info!(
"Tool Consumer: user={} tool={} args={}",
user_id, tool_name, arguments
);
// 高风险工具 → 审批(除非已被用户批准)
if !approved && crate::tools::is_high_risk(&tool_name) {
info!("高风险工具 {} 需要审批", tool_name);
ctx.approval_ctx.lock().await.insert(
user_id.clone(),
(
format!("[工具: {}]", tool_name),
context_token.clone(),
tool_name.clone(),
arguments.clone(),
Instant::now(),
),
);
match ctx.approval.create(&user_id, &tool_name).await {
Ok((code, _rx)) => {
let m = format!(
"⚠️ 操作确认\n\n技能:{}\n\n确认码:{}\n\n回复确认码以继续\n回复 0 或「取消」以取消\n(5分钟内有效)",
tool_name, code
);
// 通过消息队列发送审批提示(不直接调 send_text)
let ch = ChannelId::wechat(&user_id);
enqueue
.enqueue(PipelineMessage::llm_reply(ch, correlation_id, &m, None))
.await;
}
Err(e) => error!("创建审批失败: {}", e),
}
continue;
}
// 执行工具 — 所有工具统一在此处理
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;
}
MessageKind::ApprovalRequest { .. } => {
warn!("Tool Consumer 收到 ApprovalRequest");
}
other => warn!(
"Tool Consumer 收到不期望: {:?}",
std::mem::discriminant(&other)
),
}
}
info!("Tool Consumer 已停止");
}
// ─── 统一工具执行 ───
/// 所有工具的统一执行入口 — 仅此一处
///
/// `call_capability` 通过递归派发到此函数,需 boxing 避免无限大小。
fn execute_tool<'a>(
name: &'a str,
arguments: &'a str,
ctx: &'a DaemonCtx,
user_id: &'a str,
) -> std::pin::Pin<std::boxed::Box<dyn std::future::Future<Output = String> + Send + 'a>> {
Box::pin(async move {
// 上下文工具
match name {
"read_memories" => {
let result = ctx.memory_store.read_for(user_id, None).await;
tracing::info!("🔧 execute_tool: read_memories → {}", result);
return result;
}
"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();
}
ctx.memory_store.write_for(user_id, content).await;
let result = format!("已添加长期记忆: {}", content);
tracing::info!("🔧 execute_tool: write_memory → {}", result);
return result;
}
"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 result = ctx.memory_store.delete_for(user_id, memory_id).await;
tracing::info!("🔧 execute_tool: delete_memory({}) → {}", memory_id, result);
return result;
}
"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 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();
}
let result = ctx
.memory_store
.update_for(user_id, memory_id, content)
.await;
tracing::info!("🔧 execute_tool: update_memory({}) → {}", memory_id, result);
return result;
}
"read_summaries" => {
let sums = load_summaries(&ctx.db, user_id).await;
let result = if sums.is_empty() {
"暂无历史摘要".to_string()
} else {
sums.iter()
.enumerate()
.map(|(i, s)| format!("{}. {}", i + 1, s))
.collect::<Vec<_>>()
.join("\n---\n")
};
tracing::info!("🔧 execute_tool: read_summaries → {}条", sums.len());
return result;
}
"query_capabilities" => {
let specs = crate::tools::specs();
let result = crate::tools::build_capability_guide(&specs);
tracing::info!(
"🔧 execute_tool: query_capabilities → {}个工具",
specs.len()
);
return result;
}
"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();
if target_name.is_empty() {
return "请提供 name 参数".to_string();
}
// 防止嵌套 call_capability
if target_name == "call_capability" {
return "不支持嵌套调用 call_capability".to_string();
}
let target_args = serde_json::to_string(&crate::tools::unpack_call_params(&cp))
.unwrap_or_default();
tracing::info!(
"🔧 execute_tool: call_capability → {} args={}",
target_name,
target_args
);
// 递归派发
return execute_tool(&target_name, &target_args, ctx, user_id).await;
}
_ => {}
}
// 内置工具注册表
match crate::tools::execute(name, arguments).await {
Some(r) => {
tracing::info!(
"🔧 execute_tool: {} → success={} output={}",
name,
r.success,
r.output
);
r.output
}
None => {
tracing::warn!("🔧 execute_tool: 未知工具 {}", name);
format!("未知工具: {}", name)
}
}
})
}
// ─── Send Consumer ───
async fn send_consumer_loop(
rx: &mut tokio::sync::mpsc::Receiver<PipelineMessage>,
ctx: &DaemonCtx,
) {
info!("Send Consumer 已启动");
while let Some(msg) = rx.recv().await {
match msg.kind {
MessageKind::LLMReply {
text,
context_token,
} => {
let user_id = &msg.channel.user_id;
info!(
"Send Consumer: user={} text_len={} text={}",
user_id,
text.len(),
text
);
match ctx
.client
.send_text(user_id, &text, context_token.as_deref())
.await
{
Ok(sent_id) => {
info!(
"回复成功 user={} msg_id={}",
user_id.chars().take(5).collect::<String>(),
sent_id
);
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)
),
}
}
info!("Send Consumer 已停止");
}
// ─── 辅助函数 ───
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: &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![]
}
}
}
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![]
}
}
}
fn build_tools_list() -> Vec<serde_json::Value> {
let mut list = Vec::new();
list.push(serde_json::json!({
"type": "function", "function": {
"name": "query_capabilities",
"description": "列出所有可用工具的详细说明、参数格式和调用示例。",
"parameters": { "type": "object", "properties": {} }
}
}));
list.push(serde_json::json!({
"type": "function", "function": {
"name": "call_capability",
"description": "调用一个已注册的工具。name 为工具名。",
"parameters": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "工具名称" },
"prompt": { "type": "string", "description": "json格式参数" }
},
"required": ["name"]
}
}
}));
list.push(serde_json::json!({
"type": "function", "function": {
"name": "read_memories", "description": "读取用户的长期记忆",
"parameters": { "type": "object", "properties": {} }
}
}));
list.push(serde_json::json!({
"type": "function", "function": {
"name": "write_memory", "description": "记录用户的重要信息或偏好",
"parameters": { "type": "object", "properties": { "content": { "type": "string" } }, "required": ["content"] }
}
}));
list.push(serde_json::json!({
"type": "function", "function": {
"name": "read_summaries", "description": "读取历史会话摘要",
"parameters": { "type": "object", "properties": {} }
}
}));
list.push(serde_json::json!({
"type": "function", "function": {
"name": "delete_memory",
"description": "删除指定 id 的长期记忆。id 来自 read_memories 返回的 [id] 前缀。",
"parameters": {
"type": "object",
"properties": {
"memory_id": { "type": "integer", "description": "要删除的记忆 id" }
},
"required": ["memory_id"]
}
}
}));
list.push(serde_json::json!({
"type": "function", "function": {
"name": "update_memory",
"description": "更新指定 id 的长期记忆内容。id 来自 read_memories 返回的 [id] 前缀。",
"parameters": {
"type": "object",
"properties": {
"memory_id": { "type": "integer", "description": "要更新的记忆 id" },
"content": { "type": "string", "description": "新的记忆内容" }
},
"required": ["memory_id", "content"]
}
}
}));
let specs = crate::tools::specs();
for spec in &specs {
list.push(serde_json::json!({
"type": "function",
"function": { "name": spec.name, "description": spec.description, "parameters": spec.parameters }
}));
}
list
}