为 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)
126 lines
3.9 KiB
Rust
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());
|
|
}
|
|
}
|