- 移除无用克隆与冗余引用,减少不必要内存分配 - 规范 unwrap/expect 使用,修复可提前失败路径 - 修复 anthropic provider llm_timeout_secs 死代码并补全超时日志 - cargo fmt 统一格式
162 lines
7.2 KiB
Rust
162 lines
7.2 KiB
Rust
use std::sync::Arc;
|
||
use std::time::Duration;
|
||
|
||
use async_trait::async_trait;
|
||
use tokio::sync::{mpsc, watch};
|
||
|
||
use crate::domain::CapabilityPolicy;
|
||
use crate::tools::task::SubagentResult;
|
||
|
||
#[derive(Debug, Clone)]
|
||
pub struct ToolResult {
|
||
pub success: bool,
|
||
pub output: String,
|
||
pub error: Option<String>,
|
||
}
|
||
|
||
/// wait_for_subagents 工具等待期间的事件。
|
||
#[derive(Debug, Clone)]
|
||
pub enum WaitEvent {
|
||
/// 一个子代理完成,携带其结果
|
||
SubagentResult(SubagentResult),
|
||
/// wait 期间有新用户消息到达(已注入 history),携带新用户消息的内容列表
|
||
UserMessage(Vec<String>),
|
||
/// 等待超时
|
||
Timeout,
|
||
/// 收到取消信号(/stop),等待已优雅终止,状态已清理完毕
|
||
Cancelled,
|
||
}
|
||
|
||
/// wait_for_subagents 工具的协调器接口。
|
||
///
|
||
/// 封装了释放/重获取 serial_lock + select! 等待 + 管理等待状态的逻辑,
|
||
/// 使 wait 工具不直接依赖 gateway 模块(避免循环依赖)。
|
||
///
|
||
/// 具体实现 `SessionWaitCoordinator` 在 gateway 模块中,持有 Session 引用。
|
||
#[async_trait]
|
||
pub trait WaitCoordinator: Send + Sync + 'static {
|
||
/// 查询当前 topic 下仍处于 running 状态的子代理 task_id 列表。
|
||
fn query_pending_task_ids(&self) -> Vec<String>;
|
||
|
||
/// 尝试排空 sub_done_q 中已缓冲的子代理结果(非阻塞 drain,但方法本身是 async
|
||
/// 因为需要获取 Session 锁)。
|
||
///
|
||
/// 用于两种场景:
|
||
/// 1. wait_for_subagents 入口处:即使 DB 中无 running 子代理,
|
||
/// 队列可能仍缓冲了已完成子代理的结果(子代理完成 → send 到队列 →
|
||
/// DB 更新为 completed,但 LLM 上轮未消费队列)。
|
||
/// 2. wait() 进入 select! 前:多个子代理同时完成时,批量消费避免
|
||
/// 每个结果各触发一次 LLM 调用。
|
||
async fn try_drain_queued_results(&self) -> Vec<SubagentResult>;
|
||
|
||
/// 进入等待状态:释放 serial_lock → select! → 重获取 serial_lock。
|
||
///
|
||
/// 调用前提:serial_lock 已被执行路径获取,guard 存于 coordinator 内部。
|
||
/// 返回后:serial_lock 已被重新获取,waiting 标志已清除。
|
||
///
|
||
/// `cancel_rx`:可选的取消信号接收端。当收到信号时(/stop 命令),
|
||
/// select! 立即返回 `WaitEvent::Cancelled`,并完成完整的状态清理
|
||
///(重获取锁、回填 guard、清除 is_waiting、归还 receiver)。
|
||
/// 为 None 时退化为不检查取消(向后兼容,子代理场景)。
|
||
async fn wait(&self, timeout: Duration, cancel_rx: Option<watch::Receiver<()>>) -> WaitEvent;
|
||
}
|
||
|
||
#[derive(Clone, Default)]
|
||
pub struct ToolContext {
|
||
pub channel_name: Option<String>,
|
||
pub sender_id: Option<String>,
|
||
pub chat_id: Option<String>,
|
||
pub session_id: Option<String>,
|
||
pub topic_id: Option<String>,
|
||
pub message_id: Option<String>,
|
||
pub message_seq: Option<i64>,
|
||
/// 子代理标识,用于标注消息来源
|
||
pub subagent_description: Option<String>,
|
||
/// 当前嵌套深度(0 = 主 agent,1 = 子 agent,2 = 孙 agent...)
|
||
pub nesting_depth: u32,
|
||
/// 当前智能体自身的任务 ID(主 agent 为 None,子/孙 agent 为 Some(uuid))
|
||
pub task_id: Option<String>,
|
||
/// 父任务 ID(仅子/孙智能体有值,用于构建任务层级)
|
||
pub parent_task_id: Option<String>,
|
||
/// 当前工具调用的 ID(由 agent_loop 在执行前注入,用于精确关联 TaskStarted 事件)
|
||
pub tool_call_id: Option<String>,
|
||
/// 父智能体(主 agent 所选专家或上级子代理)的 capability 策略快照。
|
||
/// TaskTool 据此强制校验子代理加载(白/黑名单),与 spawn/resume 安全范式一致。
|
||
/// 以数据形式传递,避免 task 模块反向依赖 experts 模块。
|
||
pub parent_capability: Option<CapabilityPolicy>,
|
||
/// 端到端追踪 ID(从 InboundMessage 继承,用于 tool 执行路径的日志关联)。
|
||
/// None 表示无追踪上下文(如子代理独立执行或测试环境)。
|
||
pub trace_id: Option<String>,
|
||
/// 异步子代理完成队列的 sender(按 topic 隔离)。
|
||
/// 仅主 agent(nesting_depth=0)有值:agent_factory 构建时从 SessionHistory 注入。
|
||
/// TaskTool spawn 异步子代理后,子代理完成时通过此 sender 发送 SubagentResult,
|
||
/// 由 wait_for_subagents 工具的 receiver 端消费。
|
||
/// 子代理自身(nesting_depth>0)为 None:嵌套层不支持异步,走同步路径。
|
||
pub sub_done_sender: Option<mpsc::Sender<SubagentResult>>,
|
||
/// wait_for_subagents 工具的协调器(仅主 agent 有值)。
|
||
/// 封装了释放/重获取 serial_lock + select! 等待逻辑。
|
||
/// wait 工具通过此接口实现真等待(释放锁让 process_one 注入用户消息)。
|
||
pub wait_coordinator: Option<Arc<dyn WaitCoordinator>>,
|
||
/// 取消信号接收端(仅主 agent 有值,由 agent_factory 从 cancel_token clone 注入)。
|
||
/// wait_for_subagents 工具将其传给 coordinator.wait() 的 select!,
|
||
/// 使 /stop 命令能立即中断等待并完成状态清理。
|
||
/// watch::Receiver 可安全 clone,多个 receiver 共享同一 sender,
|
||
/// 互不影响各自的 has_changed() / changed() 状态。
|
||
pub cancel_rx: Option<watch::Receiver<()>>,
|
||
}
|
||
|
||
impl std::fmt::Debug for ToolContext {
|
||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
f.debug_struct("ToolContext")
|
||
.field("channel_name", &self.channel_name)
|
||
.field("sender_id", &self.sender_id)
|
||
.field("chat_id", &self.chat_id)
|
||
.field("session_id", &self.session_id)
|
||
.field("topic_id", &self.topic_id)
|
||
.field("message_id", &self.message_id)
|
||
.field("message_seq", &self.message_seq)
|
||
.field("subagent_description", &self.subagent_description)
|
||
.field("nesting_depth", &self.nesting_depth)
|
||
.field("task_id", &self.task_id)
|
||
.field("parent_task_id", &self.parent_task_id)
|
||
.field("tool_call_id", &self.tool_call_id)
|
||
.field("parent_capability", &self.parent_capability)
|
||
.field("trace_id", &self.trace_id)
|
||
.field("sub_done_sender", &self.sub_done_sender)
|
||
.field("wait_coordinator", &self.wait_coordinator.is_some())
|
||
.field("cancel_rx", &self.cancel_rx.is_some())
|
||
.finish()
|
||
}
|
||
}
|
||
|
||
#[async_trait]
|
||
pub trait Tool: Send + Sync + 'static {
|
||
fn name(&self) -> &str;
|
||
fn description(&self) -> &str;
|
||
fn parameters_schema(&self) -> serde_json::Value;
|
||
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult>;
|
||
|
||
async fn execute_with_context(
|
||
&self,
|
||
_context: &ToolContext,
|
||
args: serde_json::Value,
|
||
) -> anyhow::Result<ToolResult> {
|
||
self.execute(args).await
|
||
}
|
||
|
||
/// Whether this tool is side-effect free and safe to parallelize.
|
||
fn read_only(&self) -> bool {
|
||
false
|
||
}
|
||
|
||
/// Whether this tool can run alongside other concurrency-safe tools.
|
||
fn concurrency_safe(&self) -> bool {
|
||
self.read_only() && !self.exclusive()
|
||
}
|
||
|
||
/// Whether this tool should run alone even if concurrency is enabled.
|
||
fn exclusive(&self) -> bool {
|
||
false
|
||
}
|
||
}
|