Files
agcore/src/tools/base.rs
T

140 lines
4.1 KiB
Rust

//! 工具抽象接口与执行上下文。
use std::sync::Arc;
use async_trait::async_trait;
use serde_json::Value;
use tokio_util::sync::CancellationToken;
use crate::tools::error::ToolError;
use crate::tools::permission::Permission;
/// 工具执行上下文 —— 携带每次执行的运行时信息。
///
/// 字段在 Phase 2 即注入 `execute()` 签名中,防止后续扩展时出现
/// breaking change。后续阶段可扩展字段(如 `progress`、`shared_state`),
/// 但已有工具实现无需修改。
#[derive(Debug)]
pub struct ToolContext<'a> {
/// 当前对话/会话 ID,用于关联性追踪。
pub session_id: &'a str,
/// 链路追踪 ID,用于跨工具调用的耗时分布。
pub trace_id: &'a str,
/// 取消令牌,用于优雅取消正在执行的工具。
pub cancellation_token: CancellationToken,
}
impl<'a> ToolContext<'a> {
/// 创建一个新的工具执行上下文。
pub fn new(session_id: &'a str, trace_id: &'a str) -> Self {
Self {
session_id,
trace_id,
cancellation_token: CancellationToken::new(),
}
}
/// 创建一个使用给定取消令牌的上下文。
pub fn with_cancellation_token(
session_id: &'a str,
trace_id: &'a str,
token: CancellationToken,
) -> Self {
Self {
session_id,
trace_id,
cancellation_token: token,
}
}
}
/// 工具抽象接口 —— 所有工具(自定义或 MCP)最终都实现此 trait。
#[async_trait]
pub trait BaseTool: Send + Sync {
/// 工具名称(唯一标识,用于 LLM 的 tool_calls.name 匹配)。
fn name(&self) -> &str;
/// 工具描述(LLM 据此决定是否调用此工具)。
fn description(&self) -> &str;
/// 工具参数定义(JSON Schema 格式,传递给 LLM 的 tool.parameters)。
fn parameters(&self) -> Value;
/// 声明工具所需的权限列表。
fn required_permissions(&self) -> Vec<Permission> {
Vec::new()
}
/// 执行工具调用。
///
/// `ctx` 携带执行上下文(session_id、trace_id、cancellation_token),
/// 工具实现可在执行期间检查 `ctx.cancellation_token` 来支持优雅取消。
async fn execute(&self, args: Value, ctx: &ToolContext<'_>) -> Result<Value, ToolError>;
}
/// 为 `Arc<dyn BaseTool>` 提供 `Send + Sync` 包装,便于在 `Vec<Arc<dyn BaseTool>>` 中使用。
pub type ToolRef = Arc<dyn BaseTool>;
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
struct EchoTool;
#[async_trait]
impl BaseTool for EchoTool {
fn name(&self) -> &str {
"echo"
}
fn description(&self) -> &str {
"回显输入"
}
fn parameters(&self) -> Value {
json!({
"type": "object",
"properties": {
"text": { "type": "string" }
},
"required": ["text"]
})
}
async fn execute(&self, args: Value, _ctx: &ToolContext<'_>) -> Result<Value, ToolError> {
Ok(args)
}
}
#[tokio::test]
async fn test_mock_tool_execute() {
let tool = EchoTool;
let ctx = ToolContext::new("session-1", "trace-1");
let result = tool.execute(json!({"text": "hello"}), &ctx).await.unwrap();
assert_eq!(result, json!({"text": "hello"}));
}
#[tokio::test]
async fn test_default_permissions_empty() {
let tool = EchoTool;
assert!(tool.required_permissions().is_empty());
}
#[test]
fn test_tool_context_creation() {
let ctx = ToolContext::new("s1", "t1");
assert_eq!(ctx.session_id, "s1");
assert_eq!(ctx.trace_id, "t1");
assert!(!ctx.cancellation_token.is_cancelled());
}
#[test]
fn test_tool_context_cancellation() {
let token = CancellationToken::new();
token.cancel();
let ctx = ToolContext::with_cancellation_token("s1", "t1", token);
assert!(ctx.cancellation_token.is_cancelled());
}
}