Files
iAs/src/worker.rs
T
wunianxiao 937556917c refactor: worker进程 → tokio task 架构 + 多渠道消息队列 + JSON工具系统
核心变更:
- daemon: 废弃 worker 子进程模型,替换为 LLM/Tool/Send 三消费者 tokio task
- queue: 新增公平轮转 MessageQueue + QueueRunner 路由,严格 ChannelId 渠道隔离
- channel: ChannelId(platform + user_id) 统一渠道标识,替代散列 user_id+platform
- tools: 全部工具统一经由 execute_tool() → Tool Consumer 单一执行路径
- llm: chat_with_tools 返回 ChatResult,支持异步工具调用 → 保留/恢复 session
- tools: 新增 JSON 工具定义系统 (ToolDef) 及 SubprocessRunner (HTTP 模板引擎)
- bin: ias-auth 独立 JWT 生成器,ias-fetch headless Chrome 网页抓取
- 所有出站消息通过 Send Consumer 队列转发,无直接 send_text 调用
- Session TTL 清理 (5分钟),QueueRunner 通道容错
- cargo check 零警告,cargo test 23/23 通过
2026-06-09 16:32:49 +08:00

296 lines
11 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.
#![deprecated = "Worker 进程模式已废弃,请使用 daemon 模式(统一 tokio task 架构)"]
//! Worker — 无状态执行进程(已废弃)
//!
//! 此模块保留供编译参考,不再被 daemon 使用。
//! 新架构使用 daemon 内的 LLM/Tool/Send 三个消费者 tokio task。
//! 1. 连接 daemon 的 Unix Domain Socket
//! 2. 读取 TaskFrame (用户消息 + 上下文 + 环境变量)
//! 3. 注入环境变量,构建 Conversation,执行 LLM 对话 + 工具调用
//! 4. 逐帧输出结果 (usage, reply, bye)
//! 5. 高风险工具遇审批 → 发送 RequestApproval → 退出,由 daemon 重新 spawn
use crate::ipc::{OutputFrame, TaskFrame, recv_task, send_frame};
use crate::llm::{Conversation, ConversationConfig, DEFAULT_SYSTEM_PROMPT};
use crate::tools::types::SkillResult;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::net::UnixStream;
use tracing::info;
/// Worker 与工具执行器之间的共享状态
struct WorkerShared {
/// 待发送的审批请求 (tool, reason)
pending_approval: tokio::sync::Mutex<Option<(String, String)>>,
/// 本次对话中已由用户批准的工具名(避免重复审批)
approved_tool: tokio::sync::Mutex<Option<String>>,
/// 本地记忆缓存
memories: tokio::sync::Mutex<HashMap<String, Vec<String>>>,
/// 本次写入的新记忆(退出前发送 DbWrite 帧同步到 daemon
new_memories: tokio::sync::Mutex<Vec<String>>,
/// 预载的摘要
summaries: Vec<String>,
/// 用户 ID
user_id: String,
}
/// Worker 入口: 连接 daemon,处理一条消息后退出
pub async fn run(sock_path: &str) -> Result<(), String> {
let mut stream = UnixStream::connect(sock_path)
.await
.map_err(|e| format!("连接 daemon 失败 ({}): {}", sock_path, e))?;
info!("Worker 已连接 daemon");
// 1. 读 task
let task: TaskFrame = recv_task(&mut stream).await?;
info!(
"Worker 收到任务: user={} msg={:.60}",
task.user_id, task.msg.text
);
// 2. 注入环境变量
for (k, v) in &task.env {
unsafe {
std::env::set_var(k, v);
}
}
// 3. 构建 ConversationConfig
let system_prompt = if task.system_prompt.is_empty() {
DEFAULT_SYSTEM_PROMPT.to_string()
} else {
task.system_prompt.clone()
};
let model = if task.model.is_empty() {
std::env::var("DEEPSEEK_MODEL").unwrap_or_else(|_| "deepseek-v4-flash".to_string())
} else {
task.model.clone()
};
let cfg = ConversationConfig {
system_prompt,
model,
tools: Some(task.tools.clone()),
..Default::default()
};
// 4. 构建 Conversation 并加载上下文
let mut conv =
Conversation::new(cfg).map_err(|e| format!("初始化 Conversation 失败: {}", e))?;
info!("Worker LLM: {}/{}", conv.provider_name(), conv.model());
{
let session = conv.session();
let mut s = session.lock().await;
s.load_from_history(&task.history);
s.current_user_id = task.user_id.clone();
}
// 5. 共享状态
let approved_tool = task.approved_tool.clone().filter(|t| !t.is_empty());
let shared = Arc::new(WorkerShared {
pending_approval: tokio::sync::Mutex::new(None),
approved_tool: tokio::sync::Mutex::new(approved_tool),
memories: tokio::sync::Mutex::new(HashMap::from([(
task.user_id.clone(),
task.memories.clone(),
)])),
new_memories: tokio::sync::Mutex::new(Vec::new()),
summaries: task.summaries.clone(),
user_id: task.user_id.clone(),
});
// 6. 构建工具执行器
let executor = build_worker_executor(conv.session(), shared.clone());
conv.set_tool_executor(executor);
// 7. 执行 LLM 对话(带审批检测)
use crate::llm::ChatResult;
let ChatResult { reply, used_tools: _used_tools, usage, .. } = conv
.chat_with_tools(task.msg.text.clone())
.await
.map_err(|e| format!("LLM 对话失败: {}", e))?;
// 8. 检查是否有审批请求(由工具执行器设置)
let approval_req = shared.pending_approval.lock().await.take();
if let Some((tool, reason)) = approval_req {
info!("Worker 发送审批请求: tool={}", tool);
let _ = send_frame(&mut stream, &OutputFrame::RequestApproval { tool, reason }).await;
let _ = send_frame(&mut stream, &OutputFrame::Bye).await;
return Ok(());
}
// 9. 发送结果帧
// 先同步新写入的记忆到 daemon
let new_mems = shared.new_memories.lock().await;
for content in new_mems.iter() {
let _ = send_frame(
&mut stream,
&OutputFrame::DbWrite {
table: "user_memories".into(),
row: serde_json::json!({"content": content}),
},
)
.await;
}
drop(new_mems);
if let Some(u) = &usage {
let _ = send_frame(
&mut stream,
&OutputFrame::UsageReport {
model: conv.model().to_string(),
provider: conv.provider_name().to_string(),
prompt_tokens: u.prompt_tokens,
completion_tokens: u.completion_tokens,
total_tokens: u.total_tokens,
cache_hit_tokens: u.prompt_cache_hit_tokens,
cache_miss_tokens: u.prompt_cache_miss_tokens,
user_id: task.user_id.clone(),
},
)
.await;
}
let _ = send_frame(
&mut stream,
&OutputFrame::Reply {
text: reply.clone(),
},
)
.await;
let _ = send_frame(&mut stream, &OutputFrame::Bye).await;
info!(
"Worker 完成: user={} reply_len={}",
task.user_id,
reply.len()
);
Ok(())
}
/// 构建 Worker 侧的工具执行器(无 DB 连接)
fn build_worker_executor(
_session: Arc<tokio::sync::Mutex<crate::context::types::ChatSession>>,
shared: Arc<WorkerShared>,
) -> crate::llm::conversation::ToolExecutor {
Arc::new(move |name: &str, args_json: &str| {
let shared = shared.clone();
let n = name.to_string();
let args = args_json.to_string();
Box::pin(async move {
match n.as_str() {
"read_memories" => {
let cache = shared.memories.lock().await;
let mems = cache.get(&shared.user_id);
match mems {
Some(m) if !m.is_empty() => {
let lines: Vec<String> = m
.iter()
.enumerate()
.map(|(i, v)| format!("{}. {}", i + 1, v))
.collect();
Ok(lines.join("\n"))
}
_ => Ok("暂无长期记忆".to_string()),
}
}
"write_memory" => {
let params: serde_json::Value = serde_json::from_str(&args).unwrap_or_default();
let content = params.get("content").and_then(|v| v.as_str()).unwrap_or("");
if content.is_empty() {
return Ok("请提供 content 参数".to_string());
}
shared
.memories
.lock()
.await
.entry(shared.user_id.clone())
.or_default()
.push(content.to_string());
shared.new_memories.lock().await.push(content.to_string());
Ok(format!("已添加长期记忆: {}", content))
}
"read_summaries" => {
if shared.summaries.is_empty() {
Ok("暂无历史摘要".to_string())
} else {
Ok(shared
.summaries
.iter()
.enumerate()
.map(|(i, s)| format!("{}. {}", i + 1, s))
.collect::<Vec<_>>()
.join("\n---\n"))
}
}
"query_capabilities" => {
let specs = crate::tools::builtin::BuiltinRegistry::specs();
Ok(crate::tools::build_capability_guide(&specs))
}
// call_capability 或直接调用
_ => {
let (target_name, target_args) = if n == "call_capability" {
let cp: serde_json::Value = serde_json::from_str(&args).unwrap_or_default();
let name = cp
.get("name")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let unpacked = crate::tools::unpack_call_params(&cp);
(name, serde_json::to_string(&unpacked).unwrap_or_default())
} else {
(n.clone(), args.clone())
};
if target_name.is_empty() {
return Ok(format!("未知工具: {}", n));
}
// 高风险工具 → 请求审批(除非已被用户批准)
if crate::tools::builtin::BuiltinRegistry::is_high_risk(&target_name) {
let is_approved = shared.approved_tool.lock().await.as_deref() == Some(&target_name);
if !is_approved {
let reason = format!("LLM 请求执行高风险工具: {}", target_name);
*shared.pending_approval.lock().await = Some((target_name.clone(), reason));
return Ok(SkillResult::rejected(&target_name).output);
}
// 批准过 → 执行一次后清除(同一对话内同一工具不会再被拦截)
*shared.approved_tool.lock().await = None;
}
// 执行内置工具
if let Some(result) =
crate::tools::builtin::BuiltinRegistry::execute(&target_name, &target_args)
.await
{
if target_name == "manage_memos" {
let cp: serde_json::Value =
serde_json::from_str(&target_args).unwrap_or_default();
if cp.get("action").and_then(|v| v.as_str()) == Some("add") {
if let Some(content) = cp.get("content").and_then(|v| v.as_str()) {
shared
.memories
.lock()
.await
.entry(shared.user_id.clone())
.or_default()
.push(content.to_string());
shared.new_memories.lock().await.push(content.to_string());
}
}
}
return Ok(result.output);
}
Ok(format!("未知工具: {}", target_name))
}
}
})
})
}