feat(errors): 错误消息面向最终用户友好化
- memory::MemoryError 英文 → 中文 + 操作建议(namespace 拼写 / 后端存储 / 数据兼容性) - llm::LlmError 各变体补充修复建议(API key / 超时 / 上下文压缩) - tools::ToolError 各变体补充修复指引(注册工具 / 检查 Schema / 调权限 / MCP 握手) - prompt::PromptError 各变体补充修复指引(注册子模板 / 检查语法 / 调深度限制) - agent::AgentError::Config 中文化 + 提示需调用的 builder 方法名
This commit is contained in:
@@ -89,17 +89,18 @@ impl AgentBuilder {
|
||||
/// 构造 `RuntimeBundle`,校验必填字段。
|
||||
///
|
||||
/// **错误**:`provider` / `tool_registry` / `hook_executor` 任一缺失则返回
|
||||
/// `AgentError::Config("missing <field>")`,不 panic。
|
||||
/// `AgentError::Config(...)`,提示调用 `.provider(...)` / `.tool_registry(...)` /
|
||||
/// `.hook_executor(...)` 补齐。不 panic。
|
||||
pub fn build(self) -> Result<RuntimeBundle, AgentError> {
|
||||
let provider = self
|
||||
.provider
|
||||
.ok_or_else(|| AgentError::Config("missing provider".into()))?;
|
||||
.ok_or_else(|| AgentError::Config("缺少 LLM provider,请先调用 .provider(...)".into()))?;
|
||||
let tool_registry = self
|
||||
.tool_registry
|
||||
.ok_or_else(|| AgentError::Config("missing tool_registry".into()))?;
|
||||
.ok_or_else(|| AgentError::Config("缺少 tool_registry,请先调用 .tool_registry(...)(即使是空 ToolRegistry 也需要传入)".into()))?;
|
||||
let hook_executor = self
|
||||
.hook_executor
|
||||
.ok_or_else(|| AgentError::Config("missing hook_executor".into()))?;
|
||||
.ok_or_else(|| AgentError::Config("缺少 hook_executor,请先调用 .hook_executor(...)(空 HookExecutor 也可)".into()))?;
|
||||
|
||||
let config = self.config.unwrap_or_default();
|
||||
|
||||
|
||||
+1
-1
@@ -129,7 +129,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn config_not_recoverable() {
|
||||
assert!(!AgentError::Config("missing provider".into()).is_recoverable());
|
||||
assert!(!AgentError::Config("缺少 provider".into()).is_recoverable());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+15
-13
@@ -5,33 +5,35 @@ use std::time::Duration;
|
||||
/// 错误按可重试性分为两类:
|
||||
/// - **可重试**:`RateLimit`、`Timeout`、状态码 >= 500
|
||||
/// - **不可重试**:`Authentication`、`ContextLength`、状态码 4xx(除 429)
|
||||
///
|
||||
/// 错误消息面向最终用户(中文),并尽量附带可操作的修复建议(如检查 API key、减少上下文)。
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum LlmError {
|
||||
/// API 认证失败(如 API key 无效)。
|
||||
#[error("认证失败: {0}")]
|
||||
/// API 认证失败(API key 无效、过期或权限不足)。
|
||||
#[error("LLM 认证失败: {0}。请检查环境变量中的 API key(如 OPENAI_API_KEY / ANTHROPIC_API_KEY)是否正确")]
|
||||
Authentication(String),
|
||||
|
||||
/// 请求被限流,可选地附带重试等待时间。
|
||||
#[error("限流(retry_after={retry_after:?})")]
|
||||
/// 请求被限流,可选地附带重试等待时间。可重试。
|
||||
#[error("LLM 限流(服务方),建议等待 {retry_after:?} 后重试")]
|
||||
RateLimit { retry_after: Option<Duration> },
|
||||
|
||||
/// HTTP 请求失败,包含状态码和响应体。
|
||||
#[error("请求失败(status={status}): {body}")]
|
||||
/// HTTP 请求失败(网络错误或非 2xx 状态码),包含状态码与响应体。
|
||||
#[error("LLM 请求失败(HTTP {status}): {body}")]
|
||||
Request { status: u16, body: String },
|
||||
|
||||
/// 请求超时。
|
||||
#[error("请求超时(duration={duration:?})")]
|
||||
/// 请求超时。可重试。
|
||||
#[error("LLM 请求超时({duration:?})。请检查网络连接,或调大 LlmCycle 超时配置")]
|
||||
Timeout { duration: Duration },
|
||||
|
||||
/// 流式响应处理错误(预留)。
|
||||
#[error("流式响应错误: {0}")]
|
||||
/// 流式响应处理错误(SSE 解析失败、流中断等)。可重试。
|
||||
#[error("LLM 流式响应错误: {0}。可重试或改用非流式接口")]
|
||||
Stream(String),
|
||||
|
||||
/// 上下文长度超限。
|
||||
#[error("上下文超限(actual={actual}, limit={limit})")]
|
||||
/// 上下文长度超出模型窗口限制。
|
||||
#[error("LLM 上下文超限:当前 {actual} tokens > 模型上限 {limit} tokens。请减少消息历史、缩短 prompt,或启用 auto-compaction(llm::compact)")]
|
||||
ContextLength { actual: u32, limit: u32 },
|
||||
|
||||
/// 其他未分类的 LLM 调用失败。
|
||||
#[error("LLM 调用失败: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
}
|
||||
+13
-6
@@ -3,21 +3,28 @@
|
||||
use thiserror::Error;
|
||||
|
||||
/// 记忆系统错误枚举。
|
||||
///
|
||||
/// 错误消息面向最终用户(中文),并尽量附带可操作的修复建议(如检查环境变量、重试)。
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MemoryError {
|
||||
#[error("Item not found: {0}")]
|
||||
/// 按 ID 未找到指定记忆条目。可重试——通常是 namespace 拼写错误或条目已被淘汰。
|
||||
#[error("未找到记忆条目 '{0}',请检查 ID 或 namespace 是否正确")]
|
||||
NotFound(String),
|
||||
|
||||
#[error("Storage error: {0}")]
|
||||
/// 底层存储失败(磁盘满、连接断开等)。一般不可恢复,建议上层记录并告警。
|
||||
#[error("存储失败: {0},请检查后端存储是否可用(磁盘 / 网络 / 权限)")]
|
||||
Storage(String),
|
||||
|
||||
#[error("Serialization error: {0}")]
|
||||
/// 序列化 / 反序列化失败(数据结构变更、JSON 字段缺失等)。一般不可恢复。
|
||||
#[error("序列化失败: {0},请检查数据结构兼容性或升级记忆格式")]
|
||||
Serialization(String),
|
||||
|
||||
#[error("Invalid input: {0}")]
|
||||
/// 调用方传入的参数不合法(空 ID、负数容量等)。可重试——修正参数即可。
|
||||
#[error("参数无效: {0}")]
|
||||
InvalidInput(String),
|
||||
|
||||
#[error("Retrieval error: {0}")]
|
||||
/// 检索过程出错(索引重建失败、评分异常等)。可重试。
|
||||
#[error("检索失败: {0},可重试或调整 query")]
|
||||
RetrievalError(String),
|
||||
}
|
||||
|
||||
@@ -26,4 +33,4 @@ impl MemoryError {
|
||||
pub fn is_recoverable(&self) -> bool {
|
||||
matches!(self, Self::NotFound(_) | Self::RetrievalError(_))
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -2,27 +2,27 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PromptError {
|
||||
#[error("模板解析错误: {0}")]
|
||||
#[error("模板解析错误: {0}。请检查模板语法({{var}} / {{#if}} / {{#each}})")]
|
||||
Parse(String),
|
||||
|
||||
#[error("渲染错误: 变量 '{0}' 未找到")]
|
||||
#[error("渲染错误: 变量 '{0}' 未找到。请在 TemplateContext 中插入该变量")]
|
||||
VariableNotFound(String),
|
||||
|
||||
#[error("渲染错误: 引用的子模板 '{0}' 未注册")]
|
||||
#[error("渲染错误: 引用的子模板 '{0}' 未注册。请先用 PromptTemplateRegistry::register 注册该子模板")]
|
||||
PartialNotFound(String),
|
||||
|
||||
#[error("渲染错误: '{0}' 不是数组,无法遍历")]
|
||||
#[error("渲染错误: '{0}' 不是数组,无法遍历。请确认传入的是数组或先判空")]
|
||||
NotAnArray(String),
|
||||
|
||||
#[error("渲染递归超过最大深度限制 ({0})")]
|
||||
#[error("渲染递归超过最大深度限制 ({0})。请检查是否有循环 include,或调大 MAX_DEPTH")]
|
||||
MaxDepthReached(u8),
|
||||
|
||||
#[error("渲染错误: {0}")]
|
||||
Render(String),
|
||||
|
||||
#[error("消息序列校验失败: {0}")]
|
||||
#[error("消息序列校验失败: {0}。请检查消息角色顺序(例如 tool 必须在 assistant tool_call 之后)")]
|
||||
InvalidSequence(String),
|
||||
|
||||
#[error("文件读取错误: {0}")]
|
||||
#[error("文件读取错误: {0}。请检查模板文件路径与权限")]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
+12
-12
@@ -5,44 +5,44 @@ use std::sync::Arc;
|
||||
/// 工具调用过程中可能发生的所有错误。
|
||||
#[derive(thiserror::Error, Debug, Clone)]
|
||||
pub enum ToolError {
|
||||
/// 工具未注册。
|
||||
#[error("工具 '{0}' 未注册")]
|
||||
/// 工具未注册。不可恢复——需调用方先 `registry.register(...)`。
|
||||
#[error("工具 '{0}' 未注册。请先用 ToolRegistry::register(...) 注册该工具,或检查 LLM 输出的工具名拼写")]
|
||||
NotFound(String),
|
||||
|
||||
/// 工具执行失败(可恢复——文本回传 LLM)。
|
||||
#[error("工具 '{0}' 执行失败: {1}")]
|
||||
/// 工具执行失败(可恢复——文本回传 LLM 由其决定重试或放弃)。
|
||||
#[error("工具 '{0}' 执行失败: {1}。可让 LLM 调整参数后重试,或切换到备选工具")]
|
||||
ExecutionFailed(String, String),
|
||||
|
||||
/// 工具参数无效(可恢复——文本回传 LLM)。
|
||||
#[error("工具 '{0}' 参数无效: {1}")]
|
||||
#[error("工具 '{0}' 参数无效: {1}。请检查 LLM 输出的参数是否符合 BaseTool::parameters() 声明的 JSON Schema")]
|
||||
InvalidArguments(String, String),
|
||||
|
||||
/// 权限被拒绝(不可恢复——终止循环)。
|
||||
#[error("权限被拒绝: 工具 '{0}' 需要 {1} 权限")]
|
||||
#[error("权限被拒绝: 工具 '{0}' 需要 {1} 权限。请在 PermissionConfig 中显式允许,或人工确认后绕过")]
|
||||
PermissionDenied(String, String),
|
||||
|
||||
/// MCP 协议错误(不可恢复)。
|
||||
#[error("MCP 协议错误: {0}")]
|
||||
#[error("MCP 协议错误: {0}。请检查 MCP server 版本与本库兼容,或查看 server 日志")]
|
||||
McpError(String),
|
||||
|
||||
/// MCP 未初始化(不可恢复)。
|
||||
#[error("MCP 未初始化: {0}")]
|
||||
#[error("MCP 未初始化: {0}。请先调用 McpClient::initialize() 完成握手")]
|
||||
McpNotInitialized(String),
|
||||
|
||||
/// MCP 超时(不可恢复)。
|
||||
#[error("MCP 超时: {0}")]
|
||||
#[error("MCP 超时: {0}。请检查 MCP server 是否存活,或调大超时配置")]
|
||||
McpTimeout(String),
|
||||
|
||||
/// IO 错误(不可恢复)。
|
||||
#[error("IO 错误: {0}")]
|
||||
#[error("IO 错误: {0}。请检查文件路径、权限或磁盘空间")]
|
||||
Io(Arc<std::io::Error>),
|
||||
|
||||
/// 取消。
|
||||
/// 工具执行被取消。
|
||||
#[error("工具执行已取消: {0}")]
|
||||
Cancelled(String),
|
||||
|
||||
/// 其他未分类错误。
|
||||
#[error("其他错误: {0}")]
|
||||
#[error("工具调用错误: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user