Files
agcore/src/tools/error.rs
T
徐涛 6e1182e64c feat(core): 14 个公开枚举标记 #[non_exhaustive]
为 v0.2.0-rc.1 API 稳定性收尾。覆盖:
- P0 核心 IR: Message / ContentBlock / ContentBlockType / StreamEvent / HookEvent
- P0 Error: AgentError / LlmError / ToolError / MemoryError / PromptError
- P1 其他: MemoryStrategy / StepStatus / ToolChoice / ResponseFormat

明确不加的: 内部 wire-format (OpenaiChatMessage 等) /
语义已收敛 (Role/ServiceTier 等) / 使用面窄 (Permission 等)。

示例侧的 3 处 Message exhaustive match (prompt_composer /
conversation_memory_demo) 补全 `_` 通配分支,零行为变化。

验收: cargo build + clippy -D warnings + test --all-targets 全绿 (200 passed)
2026-07-05 19:47:03 +08:00

126 lines
3.9 KiB
Rust

//! 工具系统错误类型。
use std::sync::Arc;
/// 工具调用过程中可能发生的所有错误。
#[derive(thiserror::Error, Debug, Clone)]
#[non_exhaustive]
pub enum ToolError {
/// 工具未注册。不可恢复——需调用方先 `registry.register(...)`。
#[error(
"工具 '{0}' 未注册。请先用 ToolRegistry::register(...) 注册该工具,或检查 LLM 输出的工具名拼写"
)]
NotFound(String),
/// 工具执行失败(可恢复——文本回传 LLM 由其决定重试或放弃)。
#[error("工具 '{0}' 执行失败: {1}。可让 LLM 调整参数后重试,或切换到备选工具")]
ExecutionFailed(String, String),
/// 工具参数无效(可恢复——文本回传 LLM)。
#[error(
"工具 '{0}' 参数无效: {1}。请检查 LLM 输出的参数是否符合 BaseTool::parameters() 声明的 JSON Schema"
)]
InvalidArguments(String, String),
/// 权限被拒绝(不可恢复——终止循环)。
#[error(
"权限被拒绝: 工具 '{0}' 需要 {1} 权限。请在 PermissionConfig 中显式允许,或人工确认后绕过"
)]
PermissionDenied(String, String),
/// MCP 协议错误(不可恢复)。
#[error("MCP 协议错误: {0}。请检查 MCP server 版本与本库兼容,或查看 server 日志")]
McpError(String),
/// MCP 未初始化(不可恢复)。
#[error("MCP 未初始化: {0}。请先调用 McpClient::initialize() 完成握手")]
McpNotInitialized(String),
/// MCP 超时(不可恢复)。
#[error("MCP 超时: {0}。请检查 MCP server 是否存活,或调大超时配置")]
McpTimeout(String),
/// IO 错误(不可恢复)。
#[error("IO 错误: {0}。请检查文件路径、权限或磁盘空间")]
Io(Arc<std::io::Error>),
/// 工具执行被取消。
#[error("工具执行已取消: {0}")]
Cancelled(String),
/// 其他未分类错误。
#[error("工具调用错误: {0}")]
Other(String),
}
impl From<std::io::Error> for ToolError {
fn from(e: std::io::Error) -> Self {
ToolError::Io(Arc::new(e))
}
}
impl ToolError {
/// 判断错误是否可恢复——可恢复的错误回传 LLM 由其自行重试,
/// 不可恢复的错误终止自动 tool 循环并返回给调用方。
pub fn is_recoverable(&self) -> bool {
matches!(
self,
Self::ExecutionFailed(..) | Self::InvalidArguments(..) | Self::Other(_)
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_execution_failed_is_recoverable() {
let err = ToolError::ExecutionFailed("foo".into(), "boom".into());
assert!(err.is_recoverable());
}
#[test]
fn test_invalid_arguments_is_recoverable() {
let err = ToolError::InvalidArguments("foo".into(), "missing x".into());
assert!(err.is_recoverable());
}
#[test]
fn test_not_found_is_not_recoverable() {
let err = ToolError::NotFound("foo".into());
assert!(!err.is_recoverable());
}
#[test]
fn test_permission_denied_is_not_recoverable() {
let err = ToolError::PermissionDenied("foo".into(), "Shell".into());
assert!(!err.is_recoverable());
}
#[test]
fn test_mcp_error_is_not_recoverable() {
let err = ToolError::McpError("protocol".into());
assert!(!err.is_recoverable());
}
#[test]
fn test_mcp_timeout_is_not_recoverable() {
let err = ToolError::McpTimeout("foo".into());
assert!(!err.is_recoverable());
}
#[test]
fn test_io_is_not_recoverable() {
let io_err = std::io::Error::other("disk");
let err = ToolError::from(io_err);
assert!(!err.is_recoverable());
}
#[test]
fn test_other_is_recoverable() {
let err = ToolError::Other("something".into());
assert!(err.is_recoverable());
}
}