Skills 错误处理与容错
一个只能在理想情况下运行的 Skill 是脆弱的。
健壮的 Skill 需要预见常见错误,给出清晰的反馈,并尽可能自动恢复。
错误处理的三个层次
| 层次 | 发生位置 | 处理方式 |
|---|---|---|
| 输入验证错误 | 执行前 | 检查输入,若不合法直接拒绝并说明原因 |
| 运行时错误 | 脚本执行中 | 捕获异常,输出结构化错误信息 |
| 结果异常 | 执行完成后 | 验证输出是否符合预期,若不符合触发补救流程 |
在 SKILL.md 中定义错误处理策略
SKILL.md 中应当包含一个明确的错误处理章节,告诉 Claude 遇到问题时该怎么做。
实例
## 错误处理
### 输入问题
- 文件不存在:告知用户文件路径有误,请重新上传
- 文件格式不支持:列出支持的格式(pdf、docx、txt),请用户转换后重试
- 文件为空:告知文件内容为空,无法处理
### 执行问题
- 脚本运行失败:将完整错误信息展示给用户,不要隐藏
- 依赖未安装:先尝试自动安装(pip install),若失败则告知用户手动安装
- 超时:若超过 60 秒未完成,输出当前进度并询问用户是否继续等待
### 结果问题
- 输出文件为空:重新检查处理逻辑,向用户说明可能原因
- 输出格式不符合预期:展示实际输出,询问用户是否满意
### 输入问题
- 文件不存在:告知用户文件路径有误,请重新上传
- 文件格式不支持:列出支持的格式(pdf、docx、txt),请用户转换后重试
- 文件为空:告知文件内容为空,无法处理
### 执行问题
- 脚本运行失败:将完整错误信息展示给用户,不要隐藏
- 依赖未安装:先尝试自动安装(pip install),若失败则告知用户手动安装
- 超时:若超过 60 秒未完成,输出当前进度并询问用户是否继续等待
### 结果问题
- 输出文件为空:重新检查处理逻辑,向用户说明可能原因
- 输出格式不符合预期:展示实际输出,询问用户是否满意
脚本中的标准错误处理模式
Python 脚本应使用 try-except 结构,并统一返回包含 status 字段的 JSON。
实例
# 文件路径:scripts/error_handling_demo.py
import json
import sys
import os
class SkillError(Exception):
"""Skill 自定义异常基类"""
def __init__(self, message: str, code: str):
self.message = message
self.code = code # 错误码,便于程序判断错误类型
super().__init__(message)
class InputError(SkillError):
"""输入验证错误"""
pass
class ProcessError(SkillError):
"""处理过程错误"""
pass
def validate_input(file_path: str) -> None:
"""验证输入,出错时抛出 InputError"""
if not file_path:
raise InputError("未提供文件路径", "MISSING_FILE")
if not os.path.exists(file_path):
raise InputError(f"文件不存在:{file_path}", "FILE_NOT_FOUND")
if os.path.getsize(file_path) == 0:
raise InputError(f"文件内容为空:{file_path}", "EMPTY_FILE")
def process(file_path: str) -> dict:
"""主处理逻辑"""
try:
validate_input(file_path)
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
# 实际处理逻辑(此处简化为行数统计)
lines = content.split("\n")
return {
"status": "success",
"data": {
"file": file_path,
"lines": len(lines),
"chars": len(content)
}
}
except InputError as e:
# 输入错误:用户可以修正后重试
return {
"status": "input_error",
"code": e.code,
"message": e.message,
"hint": "请检查文件路径是否正确,文件是否存在且非空"
}
except UnicodeDecodeError:
# 编码错误:给出具体的修复建议
return {
"status": "process_error",
"code": "ENCODING_ERROR",
"message": "文件编码不是 UTF-8,无法读取",
"hint": "请将文件另存为 UTF-8 编码后重试"
}
except Exception as e:
# 未预期的错误:完整记录,便于调试
return {
"status": "unexpected_error",
"code": "UNKNOWN",
"message": str(e),
"hint": "请将上述错误信息反馈给开发者"
}
if __name__ == "__main__":
file_path = sys.argv[1] if len(sys.argv) > 1 else ""
result = process(file_path)
print(json.dumps(result, ensure_ascii=False, indent=2))
import json
import sys
import os
class SkillError(Exception):
"""Skill 自定义异常基类"""
def __init__(self, message: str, code: str):
self.message = message
self.code = code # 错误码,便于程序判断错误类型
super().__init__(message)
class InputError(SkillError):
"""输入验证错误"""
pass
class ProcessError(SkillError):
"""处理过程错误"""
pass
def validate_input(file_path: str) -> None:
"""验证输入,出错时抛出 InputError"""
if not file_path:
raise InputError("未提供文件路径", "MISSING_FILE")
if not os.path.exists(file_path):
raise InputError(f"文件不存在:{file_path}", "FILE_NOT_FOUND")
if os.path.getsize(file_path) == 0:
raise InputError(f"文件内容为空:{file_path}", "EMPTY_FILE")
def process(file_path: str) -> dict:
"""主处理逻辑"""
try:
validate_input(file_path)
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
# 实际处理逻辑(此处简化为行数统计)
lines = content.split("\n")
return {
"status": "success",
"data": {
"file": file_path,
"lines": len(lines),
"chars": len(content)
}
}
except InputError as e:
# 输入错误:用户可以修正后重试
return {
"status": "input_error",
"code": e.code,
"message": e.message,
"hint": "请检查文件路径是否正确,文件是否存在且非空"
}
except UnicodeDecodeError:
# 编码错误:给出具体的修复建议
return {
"status": "process_error",
"code": "ENCODING_ERROR",
"message": "文件编码不是 UTF-8,无法读取",
"hint": "请将文件另存为 UTF-8 编码后重试"
}
except Exception as e:
# 未预期的错误:完整记录,便于调试
return {
"status": "unexpected_error",
"code": "UNKNOWN",
"message": str(e),
"hint": "请将上述错误信息反馈给开发者"
}
if __name__ == "__main__":
file_path = sys.argv[1] if len(sys.argv) > 1 else ""
result = process(file_path)
print(json.dumps(result, ensure_ascii=False, indent=2))
# 文件存在时的成功输出:
{
"status": "success",
"data": {
"file": "/mnt/user-data/uploads/runoob.txt",
"lines": 128,
"chars": 5432
}
}
# 文件不存在时的错误输出:
{
"status": "input_error",
"code": "FILE_NOT_FOUND",
"message": "文件不存在:/mnt/user-data/uploads/missing.txt",
"hint": "请检查文件路径是否正确,文件是否存在且非空"
}
依赖自动安装的容错模式
当脚本依赖的 Python 包未安装时,可以在脚本中自动尝试安装。
实例
# 文件路径:scripts/auto_install_demo.py
import subprocess
import sys
def ensure_package(package_name: str, import_name: str = None) -> bool:
"""
确保 Python 包已安装,若未安装则自动安装
参数:
package_name: pip 安装时使用的包名(如 "python-docx")
import_name: import 时使用的名称(如 "docx"),默认与 package_name 相同
"""
import_name = import_name or package_name
try:
__import__(import_name)
return True # 已安装
except ImportError:
print(f"正在安装依赖:{package_name}...")
result = subprocess.run(
[sys.executable, "-m", "pip", "install", package_name,
"--break-system-packages", "--quiet"],
capture_output=True, text=True
)
if result.returncode == 0:
print(f"安装成功:{package_name}")
return True
else:
print(f"安装失败:{package_name}")
print(result.stderr)
return False
# 使用示例:在脚本开头确保所有依赖已就绪
if not ensure_package("pandas"):
sys.exit(1)
if not ensure_package("openpyxl"):
sys.exit(1)
# 依赖就绪后,正常 import
import pandas as pd
print("所有依赖已就绪,开始执行...")
import subprocess
import sys
def ensure_package(package_name: str, import_name: str = None) -> bool:
"""
确保 Python 包已安装,若未安装则自动安装
参数:
package_name: pip 安装时使用的包名(如 "python-docx")
import_name: import 时使用的名称(如 "docx"),默认与 package_name 相同
"""
import_name = import_name or package_name
try:
__import__(import_name)
return True # 已安装
except ImportError:
print(f"正在安装依赖:{package_name}...")
result = subprocess.run(
[sys.executable, "-m", "pip", "install", package_name,
"--break-system-packages", "--quiet"],
capture_output=True, text=True
)
if result.returncode == 0:
print(f"安装成功:{package_name}")
return True
else:
print(f"安装失败:{package_name}")
print(result.stderr)
return False
# 使用示例:在脚本开头确保所有依赖已就绪
if not ensure_package("pandas"):
sys.exit(1)
if not ensure_package("openpyxl"):
sys.exit(1)
# 依赖就绪后,正常 import
import pandas as pd
print("所有依赖已就绪,开始执行...")
自动安装只适用于开发和轻量级场景。在生产环境的 Skill 中,应将依赖写入文档,由用户预先安装,而不是在运行时自动安装。
错误信息的书写规范
好的错误信息能帮助用户快速定位问题,差的错误信息只会让人困惑。
| 维度 | 差的写法 | 好的写法 |
|---|---|---|
| 描述问题 | "出错了" | "文件 report.csv 不存在于上传目录" |
| 说明原因 | (无) | "可能是文件名拼写有误,或文件尚未上传" |
| 提供行动 | (无) | "请重新上传文件,或检查文件名是否正确" |
实例
# 差的错误信息
raise Exception("Error")
# 好的错误信息
raise InputError(
"文件 runoob_data.csv 未在上传目录中找到。"
"可能原因:文件名拼写有误,或文件尚未上传。"
"请重新上传文件后再试。",
code="FILE_NOT_FOUND"
)
raise Exception("Error")
# 好的错误信息
raise InputError(
"文件 runoob_data.csv 未在上传目录中找到。"
"可能原因:文件名拼写有误,或文件尚未上传。"
"请重新上传文件后再试。",
code="FILE_NOT_FOUND"
)
