feat(errors): 错误消息面向最终用户友好化

- memory::MemoryError 英文 → 中文 + 操作建议(namespace 拼写 / 后端存储 / 数据兼容性)
- llm::LlmError 各变体补充修复建议(API key / 超时 / 上下文压缩)
- tools::ToolError 各变体补充修复指引(注册工具 / 检查 Schema / 调权限 / MCP 握手)
- prompt::PromptError 各变体补充修复指引(注册子模板 / 检查语法 / 调深度限制)
- agent::AgentError::Config 中文化 + 提示需调用的 builder 方法名
This commit is contained in:
徐涛
2026-07-04 06:21:31 +08:00
parent 9f5e8702a2
commit 4fdc62754c
6 changed files with 53 additions and 43 deletions
+5 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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-compactionllm::compact")]
ContextLength { actual: u32, limit: u32 },
/// 其他未分类的 LLM 调用失败。
#[error("LLM 调用失败: {0}")]
Other(String),
}
}
+13 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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),
}