1. OpenClaw技能系统概述
OpenClaw技能系统是一个革命性的AI能力扩展框架,它通过自定义技能的方式,让AI Agent能够突破通用能力的限制,获得专业领域的知识和工具。这个系统的设计理念可以概括为"简单、灵活、可组合"。
1.1 核心设计哲学
OpenClaw技能系统遵循"约定优于配置"的设计哲学。这意味着开发者只需要遵循一些简单的约定和规范,就能快速创建功能完备的技能,而不需要编写大量繁琐的配置文件或进行复杂的注册流程。
这种设计带来了几个显著优势:
- 降低开发门槛:开发者可以专注于业务逻辑本身,而不是框架配置
- 提高开发效率:标准化的结构和规范减少了决策成本
- 便于维护:统一的约定使得不同技能之间保持一致性
1.2 系统架构解析
OpenClaw技能系统采用分层架构设计,各层职责明确:
- AI Agent决策层:负责理解用户意图并选择合适的技能
- Skill Registry层:维护技能元数据和权限验证
- Skill Execution层:负责实际的脚本执行
- External Resources层:技能访问的外部资源
这种分层设计确保了系统的灵活性、安全性和可维护性。每个层级都有明确的边界和职责,修改一个层级不会影响其他层级的功能。
1.3 技能目录结构
一个标准的OpenClaw技能遵循以下目录结构:
code复制my-skill/
├── SKILL.md # 技能定义文件(必需)
├── scripts/ # 脚本目录
│ ├── main.py # 主脚本
│ └── utils.py # 辅助模块
├── tests/ # 测试目录(可选)
│ └── test_main.py
└── README.md # 说明文档(可选)
这种结构简单清晰,便于开发者快速上手。其中,SKILL.md是技能的核心定义文件,包含技能的名称、描述、参数说明等关键信息。
1.4 技能生命周期
技能从创建到执行会经历完整的生命周期:
- 创建阶段:设计目录结构和文件组织
- 定义阶段:编写清晰准确的SKILL.md
- 开发阶段:实现健壮的脚本逻辑
- 测试阶段:验证功能的正确性和健壮性
- 执行阶段:处理各种边界情况和错误
理解这个生命周期对于开发高质量技能至关重要。每个阶段都有其特定的关注点和最佳实践。
1.5 技能发现与调用机制
OpenClaw采用目录扫描的方式发现技能。系统启动时,会扫描skills/目录下的所有子目录,识别其中的SKILL.md文件,并将技能信息加载到内存中。
当AI模型处理用户请求时,会根据技能的描述信息判断是否需要调用某个技能。整个调用流程如下:
- 用户发送请求(如"帮我查一下北京今天的天气")
- AI分析意图并查询匹配的技能
- 返回匹配的技能信息
- 生成调用参数并执行技能
- 运行脚本并返回结果
- AI整合数据生成自然语言回复
这种分工让每个组件都专注于自己擅长的领域:AI负责理解意图和生成回复,技能脚本负责执行具体操作。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技能开发全流程指南
2.1 需求分析方法论
开发一个高质量的技能始于清晰的需求分析。以下是进行需求分析的关键维度:
用户画像分析:
- 目标用户是谁?
- 他们的技术水平如何?
- 他们期望什么样的交互方式?
使用场景分析:
- 用户在什么情况下会触发这个技能?
- 是主动调用还是被动触发?
- 使用频率如何?
功能边界定义:
- 技能应该做什么?
- 不应该做什么?
- 哪些功能是核心的,哪些是可选的?
数据来源分析:
- 需要访问哪些数据源?
- 是本地文件、外部API还是数据库?
错误处理策略:
- 可能出现哪些异常情况?
- 如何向用户反馈错误信息?
以天气查询技能为例的需求分析矩阵:
| 维度 | 分析结果 |
|---|---|
| 用户画像 | 普通用户,技术水平不限,期望自然语言交互 |
| 使用场景 | 查询当前天气、未来预报、空气质量等 |
| 功能边界 | 仅支持天气查询,不支持天气预警推送 |
| 数据来源 | 第三方天气API(如wttr.in、Open-Meteo) |
| 错误处理 | 城市不存在、API超时、网络异常等 |
2.2 架构设计原则
完成需求分析后,需要进行合理的架构设计。优秀的架构设计应遵循以下原则:
- 单一职责原则:每个模块只做一件事,降低耦合度
- 开放封闭原则:对扩展开放,对修改封闭
- 依赖倒置原则:高层模块不依赖低层模块,两者都依赖抽象接口
对于技能开发,推荐采用三层架构:
-
表现层(SKILL.md):
- 技能描述
- 参数定义
- 使用示例
-
业务层(main.py):
- 参数解析
- 业务逻辑
- 结果封装
-
基础层(utils.py):
- API客户端
- 日志工具
- 错误处理
这种分层架构将不同关注点分离,使代码更加清晰、易于维护。
2.3 接口设计规范
技能的接口设计直接影响AI模型的使用体验。优秀的接口设计应遵循:
- 参数最小化:只要求必要的参数,可选参数提供默认值
- 语义清晰:参数名称和描述要明确无歧义
- 类型明确:明确参数类型(字符串、数字等)
- 返回结构化:返回结构化的数据,便于AI处理
天气查询技能的接口设计示例:
参数设计:
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| city | string | 是 | - | 城市名称,支持中文和英文 |
| days | number | 否 | 1 | 预报天数,范围1-7 |
| units | string | 否 | metric | 温度单位:metric(摄氏)或imperial(华氏) |
返回值设计:
json复制{
"code": 0,
"message": "success",
"data": {
"city": "北京",
"current": {
"temp": 22,
"condition": "晴",
"humidity": 45,
"wind": "东北风3级"
},
"forecast": [
{"date": "2026-03-20", "high": 25, "low": 15, "condition": "多云"}
]
}
}
3. 脚本开发最佳实践
3.1 标准脚本模板
一个规范的技能脚本应包含以下结构:
python复制#!/usr/bin/env python3
"""技能名称 - 简短描述
详细描述技能的功能、使用场景和注意事项。
Dependencies:
pip install requests # 外部依赖
Environment:
API_KEY: API密钥(如果需要)
"""
import argparse
import json
import logging
import sys
from typing import Optional
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def parse_args():
"""解析命令行参数"""
parser = argparse.ArgumentParser(description='技能描述')
parser.add_argument('--param1', required=True, help='参数1说明')
parser.add_argument('--param2', default='default', help='参数2说明')
return parser.parse_args()
def main():
"""主函数"""
args = parse_args()
try:
# 业务逻辑
result = do_something(args.param1, args.param2)
# 输出结果
print(json.dumps({
"code": 0,
"message": "success",
"data": result
}, ensure_ascii=False))
except Exception as e:
logger.exception("执行失败")
print(json.dumps({
"code": 1,
"message": str(e),
"data": None
}))
sys.exit(1)
if __name__ == "__main__":
main()
这个模板包含了技能脚本的关键组成部分:
- 文档字符串:说明模块用途
- 导入声明:列出所有依赖
- 日志配置:设置日志格式和级别
- 参数解析:处理命令行参数
- 主函数:包含业务逻辑和错误处理
- 结构化输出:确保返回格式统一
3.2 参数解析与验证
3.2.1 命令行参数解析
Python的argparse模块是处理命令行参数的标准工具:
python复制import argparse
def parse_args():
"""解析命令行参数"""
parser = argparse.ArgumentParser(
description='天气查询技能 - 获取指定城市的天气信息'
)
# 必填参数
parser.add_argument(
'--city',
required=True,
type=str,
help='城市名称,支持中文(如"北京")或英文(如"Beijing")'
)
# 可选参数 - 整数类型
parser.add_argument(
'--days',
type=int,
default=1,
choices=range(1, 8),
help='预报天数,范围1-7,默认为1'
)
# 可选参数 - 枚举类型
parser.add_argument(
'--units',
type=str,
default='metric',
choices=['metric', 'imperial'],
help='温度单位:metric(摄氏度)或imperial(华氏度)'
)
return parser.parse_args()
argparse支持的功能包括:
- required=True指定必填参数
- default设置默认值
- choices限制可选值范围
- type自动类型转换
- help参数说明文本
3.2.2 参数验证策略
参数验证是保证技能健壮性的关键环节。建议采用"尽早验证,明确反馈"的策略:
python复制import re
from typing import Optional
def validate_city(city: str) -> str:
"""验证城市名称
Args:
city: 用户输入的城市名称
Returns:
验证通过的城市名称(标准化后)
Raises:
ValueError: 城市名称无效
"""
if not city or not city.strip():
raise ValueError("城市名称不能为空")
# 去除首尾空格
city = city.strip()
# 长度限制
if len(city) > 50:
raise ValueError("城市名称过长,请输入有效的城市名")
# 字符验证(支持中英文、空格、连字符)
pattern = r'^[\u4e00-\u9fa5a-zA-Z\s\-]+$'
if not re.match(pattern, city):
raise ValueError(
f"城市名称包含无效字符: {city}。"
"请使用中文或英文名称,如'北京'或'Beijing'"
)
return city
def validate_days(days: int) -> int:
"""验证预报天数
Args:
days: 用户输入的天数
Returns:
验证通过的天数
Raises:
ValueError: 天数无效
"""
if not isinstance(days, int):
raise ValueError(f"天数必须是整数,当前类型: {type(days)}")
if days < 1 or days > 7:
raise ValueError(
f"预报天数必须在1-7之间,当前值: {days}"
)
return days
验证函数的设计要点:
- 每个验证函数专注于一个参数
- 验证失败时抛出ValueError并提供明确错误信息
- 返回标准化后的参数值
- 使用类型注解提高代码可读性
3.3 错误处理机制
3.3.1 错误分类与处理
技能执行中可能遇到的错误可分为几类:
| 错误类型 | 示例 | 处理策略 |
|---|---|---|
| 参数错误 | 城市名称为空、天数超范围 | 立即返回错误,提示正确格式 |
| 网络错误 | API超时、连接失败 | 重试机制,提示用户稍后重试 |
| 业务错误 | 城市不存在、无数据 | 返回友好提示,建议替代方案 |
| 系统错误 | 内存不足、权限问题 | 记录日志,返回通用错误信息 |
3.3.2 异常处理实现
实现一个完整的错误处理体系:
python复制from enum import Enum
from typing import Optional, Any
class ErrorCode(Enum):
"""错误码枚举"""
SUCCESS = 0
INVALID_PARAM = 1001
CITY_NOT_FOUND = 1002
API_ERROR = 2001
NETWORK_ERROR = 2002
INTERNAL_ERROR = 9999
class SkillError(Exception):
"""技能执行错误基类"""
def __init__(
self,
code: ErrorCode,
message: str,
detail: Optional[str] = None
):
self.code = code
self.message = message
self.detail = detail
super().__init__(message)
def to_dict(self) -> dict:
"""转换为字典格式"""
result = {
"code": self.code.value,
"message": self.message
}
if self.detail:
result["detail"] = self.detail
return result
class ParameterError(SkillError):
"""参数错误"""
def __init__(self, message: str, detail: Optional[str] = None):
super().__init__(ErrorCode.INVALID_PARAM, message, detail)
def handle_error(error: Exception):
"""统一错误处理
Args:
error: 异常对象
"""
if isinstance(error, SkillError):
logger.warning(f"技能错误: {error.message}", extra={"detail": error.detail})
output_result(
code=error.code.value,
message=error.message,
data={"detail": error.detail} if error.detail else None
)
else:
logger.exception("未预期的错误")
output_result(
code=ErrorCode.INTERNAL_ERROR.value,
message="内部错误,请稍后重试",
data=None
)
sys.exit(1)
这个错误处理体系的特点:
- 使用枚举定义错误码,便于统一管理
- 基类SkillError提供统一的错误格式
- 特定错误类型继承自基类,添加特定逻辑
- handle_error函数提供统一的错误处理入口
3.3.3 重试机制实现
对于网络请求等可能临时失败的操作,实现重试机制:
python复制import time
import functools
from typing import Callable, Type, Tuple
def retry(
max_attempts: int = 3,
delay: float = 1.0,
backoff: float = 2.0,
exceptions: Tuple[Type[Exception], ...] = (Exception,)
):
"""重试装饰器
Args:
max_attempts: 最大尝试次数
delay: 初始延迟(秒)
backoff: 延迟增长因子
exceptions: 触发重试的异常类型
Returns:
装饰后的函数
"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs):
current_delay = delay
last_exception = None
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except exceptions as e:
last_exception = e
if attempt < max_attempts:
logger.warning(
f"第{attempt}次尝试失败: {e},"
f"{current_delay:.1f}秒后重试"
)
time.sleep(current_delay)
current_delay *= backoff
else:
logger.error(
f"已达到最大重试次数{max_attempts},"
f"最后错误: {e}"
)
raise last_exception
return wrapper
return decorator
# 使用示例
@retry(max_attempts=3, delay=1.0, exceptions=(APIError,))
def fetch_weather_data(city: str) -> dict:
"""获取天气数据(带重试)"""
# API调用逻辑
pass
重试装饰器的特点:
- 支持配置最大尝试次数、初始延迟和延迟增长因子
- 可以指定触发重试的异常类型
- 使用指数退避策略避免对API造成过大压力
- 记录详细的重试日志便于问题排查
3.4 日志记录最佳实践
3.4.1 日志级别与使用场景
Python的logging模块提供五个标准日志级别:
| 级别 | 数值 | 使用场景 |
|---|---|---|
| DEBUG | 10 | 详细调试信息,仅开发环境使用 |
| INFO | 20 | 关键流程信息,如技能启动、参数解析完成 |
| WARNING | 30 | 警告信息,如使用了默认值、API响应较慢 |
| ERROR | 40 | 错误信息,如API调用失败、数据解析错误 |
| CRITICAL | 50 | 严重错误,如无法恢复的系统错误 |
3.4.2 日志配置实现
python复制import logging
import sys
from pathlib import Path
def setup_logging(
name: str,
level: int = logging.INFO,
log_file: str = None
) -> logging.Logger:
"""配置日志系统
Args:
name: 日志记录器名称
level: 日志级别
log_file: 日志文件路径(可选)
Returns:
配置好的Logger对象
"""
logger = logging.getLogger(name)
logger.setLevel(level)
# 日志格式
formatter = logging.Formatter(
fmt='%(asctime)s | %(name)s | %(levelname)s | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 控制台处理器
console_handler = logging.StreamHandler(sys.stderr)
console_handler.setLevel(level)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# 文件处理器(可选)
if log_file:
log_path = Path(log_file)
log_path.parent.mkdir(parents=True, exist_ok=True)
file_handler = logging.FileHandler(log_file, encoding='utf-8')
file_handler.setLevel(level)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
return logger
# 初始化日志
logger = setup_logging(
name='weather_skill',
level=logging.INFO,
log_file='/var/log/openclaw/skills/weather.log'
)
日志配置的关键点:
- 同时支持控制台和文件输出
- 设置合理的日志格式和时间格式
- 确保日志目录存在并有写入权限
- 使用UTF-8编码避免中文乱码
3.4.3 结构化日志实现
对于复杂技能,建议使用结构化日志(JSON格式):
python复制import json
from datetime import datetime
class StructuredFormatter(logging.Formatter):
"""结构化日志格式化器"""
def format(self, record: logging.LogRecord) -> str:
log_data = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
"module": record.module,
"function": record.funcName,
"line": record.lineno
}
# 添加额外字段
if hasattr(record, 'extra_data'):
log_data["data"] = record.extra_data
# 添加异常信息
if record.exc_info:
log_data["exception"] = self.formatException(record.exc_info)
return json.dumps(log_data, ensure_ascii=False)
def log_with_data(
logger: logging.Logger,
level: int,
message: str,
**kwargs
):
"""带额外数据的日志记录
Args:
logger: 日志记录器
level: 日志级别
message: 日志消息
**kwargs: 额外数据
"""
extra = {'extra_data': kwargs} if kwargs else {}
logger.log(level, message, extra=extra)
# 使用示例
log_with_data(
logger,
logging.INFO,
"天气查询成功",
city="北京",
response_time_ms=150,
api_provider="wttr.in"
)
结构化日志的优势:
- 便于日志分析和监控
- 可以附加丰富的上下文信息
- 易于与日志管理系统集成
- 支持复杂的查询和过滤
4. 实战案例:天气查询技能
4.1 完整需求分析
我们将开发一个完整的天气查询技能,功能需求如下:
核心功能:
- 查询指定城市的当前天气
- 支持未来1-7天的天气预报
- 支持摄氏度和华氏度切换
数据来源:
- 主要使用wttr.in免费API,无需API Key
- 备选方案:Open-Meteo API
用户体验要求:
- 支持中英文城市名
- 友好的错误提示
- 结构化的返回数据
4.2 SKILL.md编写规范
SKILL.md是技能的核心定义文件,内容如下:
markdown复制---
name: weather
description: "Get current weather and forecasts via wttr.in or Open-Meteo. Use when:
user asks about weather, temperature, or forecasts for any location. NOT for:
historical weather data, severe weather alerts, or detailed meteorological analysis.
No API key needed."
---
# Weather Skill
Get current weather and forecasts for any location worldwide.
## When to Use
✅ **USE this skill when:**
- User asks about current weather in a city
- User wants weather forecast for upcoming days
- User needs temperature, humidity, or wind information
❌ **DON'T use this skill when:**
- User asks about historical weather data
- User needs severe weather alerts
- User wants detailed meteorological analysis
## Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| city | string | Yes | - | City name (Chinese or English) |
| days | number | No | 1 | Forecast days (1-7) |
| units | string | No | metric | Temperature unit: metric/imperial |
## Commands
### Basic usage
```bash
python3 scripts/weather.py --city "北京"
With forecast
bash复制python3 scripts/weather.py --city "Beijing" --days 3
Fahrenheit units
bash复制python3 scripts/weather.py --city "New York" --units imperial
Response
json复制{
"code": 0,
"message": "success",
"data": {
"city": "北京",
"current": {
"temp": 22,
"condition": "晴",
"humidity": 45,
"wind": "东北风 3级"
},
"forecast": [...]
}
}
Notes
- No API key required
- Supports Chinese and English city names
- Rate limit: ~1000 requests/day
code复制
SKILL.md的关键要素:
1. 元信息:name和description是AI发现技能的关键
2. 使用场景:明确说明何时使用/不使用该技能
3. 参数说明:详细描述每个参数的类型、是否必填等
4. 命令示例:提供典型用法示例
5. 响应格式:展示标准的返回数据结构
6. 注意事项:记录重要限制和说明
### 4.3 完整脚本实现
天气查询技能的完整实现:
```python
#!/usr/bin/env python3
"""Weather Skill - Get current weather and forecasts
A skill for querying weather information using wttr.in API.
Supports Chinese and English city names, temperature unit conversion,
and multi-day forecasts.
Dependencies:
pip install requests
Author: OpenClaw Team
"""
import argparse
import json
import logging
import re
import sys
from dataclasses import dataclass
from typing import Optional, List
from enum import Enum
import requests
# 日志配置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s | %(levelname)s | %(message)s'
)
logger = logging.getLogger(__name__)
class ErrorCode(Enum):
"""错误码枚举"""
SUCCESS = 0
INVALID_PARAM = 1001
CITY_NOT_FOUND = 1002
API_ERROR = 2001
NETWORK_ERROR = 2002
@dataclass
class CurrentWeather:
"""当前天气数据"""
temp: int
feels_like: int
condition: str
humidity: int
wind_speed: int
wind_direction: str
visibility: int
@dataclass
class ForecastDay:
"""预报数据"""
date: str
max_temp: int
min_temp: int
condition: str
precipitation: float
@dataclass
class WeatherResult:
"""天气查询结果"""
city: str
current: CurrentWeather
forecast: List[ForecastDay]
def validate_city(city: str) -> str:
"""验证城市名称"""
if not city or not city.strip():
raise ValueError("城市名称不能为空")
city = city.strip()
if len(city) > 50:
raise ValueError("城市名称过长")
pattern = r'^[\u4e00-\u9fa5a-zA-Z\s\-]+$'
if not re.match(pattern, city):
raise ValueError(f"城市名称包含无效字符: {city}")
return city
def validate_days(days: int) -> int:
"""验证预报天数"""
if not isinstance(days, int) or days < 1 or days > 7:
raise ValueError("预报天数必须在1-7之间")
return days
def fetch_weather(city: str, days: int = 1) -> dict:
"""从wttr.in获取天气数据"""
url = f"https://wttr.in/{city}"
params = {
"format": "j1",
"lang": "zh"
}
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
return response.json()
except requests.Timeout:
raise RuntimeError("API请求超时,请稍后重试")
except requests.HTTPError as e:
if e.response.status_code == 404:
raise ValueError(f"未找到城市: {city}")
raise RuntimeError(f"API错误: {e}")
except requests.RequestException as e:
raise RuntimeError(f"网络错误: {e}")
def parse_weather_data(data: dict, units: str = 'metric') -> WeatherResult:
"""解析天气数据"""
current = data.get('current_condition', [{}])[0]
forecast_list = data.get('weather', [])
# 解析当前天气
temp = int(current.get('temp_C', 0))
feels_like = int(current.get('FeelsLikeC', 0))
if units == 'imperial':
temp = int(temp * 9/5 + 32)
feels_like = int(feels_like * 9/5 + 32)
current_weather = CurrentWeather(
temp=temp,
feels_like=feels_like,
condition=current.get('lang_zh', [{}])[0].get('value', current.get('weatherDesc', [{}])[0].get('value', '未知')),
humidity=int(current.get('humidity', 0)),
wind_speed=int(current.get('windspeedKmph', 0)),
wind_direction=current.get('winddir16Point', 'N'),
visibility=int(current.get('visibility', 0))
)
# 解析预报
forecast = []
for day_data in forecast_list[:7]:
max_temp = int(day_data.get('maxtempC', 0))
min_temp = int(day_data.get('mintempC', 0))
if units == 'imperial':
max_temp = int(max_temp * 9/5 + 32)
min_temp = int(min_temp * 9/5 + 32)
hourly = day_data.get('hourly', [{}])[0]
forecast.append(ForecastDay(
date=day_data.get('date', ''),
max_temp=max_temp,
min_temp=min_temp,
condition=hourly.get('lang_zh', [{}])[0].get('value', '未知'),
precipitation=float(hourly.get('precipMM', 0))
))
return WeatherResult(
city=data.get('nearest_area', [{}])[0].get('areaName', [{}])[0].get('value', city),
current=current_weather,
forecast=forecast
)
def output_result(code: int, message: str, data: Optional[dict] = None):
"""输出结构化结果"""
result = {"code": code, "message": message, "data": data}
print(json.dumps(result, ensure_ascii=False, default=str))
def main():
"""主函数"""
parser = argparse.ArgumentParser(description='天气查询技能')
parser.add_argument('--city', required=True, help='城市名称')
parser.add_argument('--days', type=int, default=1, help='预报天数 (1-7)')
parser.add_argument('--units', default='metric', choices=['metric', 'imperial'], help='温度单位')
args = parser.parse_args()
try:
# 参数验证
city = validate_city(args.city)
days = validate_days(args.days)
logger.info(f"查询天气: city={city}, days={days}, units={args.units}")
# 获取数据
raw_data = fetch_weather(city, days)
# 解析数据
result = parse_weather_data(raw_data, args.units)
# 输出结果
output_result(
code=0,
message="success",
data={
"city": result.city,
"current": {
"temp": result.current.temp,
"feels_like": result.current.feels_like,
"condition": result.current.condition,
"humidity": result.current.humidity,
"wind": f"{result.current.wind_direction} {result.current.wind_speed}km/h"
},
"forecast": [
{
"date": f.date,
"high": f.max_temp,
"low": f.min_temp,
"condition": f.condition
}
for f in result.forecast[:days]
]
}
)
logger.info(f"查询成功: {result.city}")
except ValueError as e:
logger.warning(f"参数错误: {e}")
output_result(ErrorCode.INVALID_PARAM.value, str(e))
sys.exit(1)
except RuntimeError as e:
logger.error(f"运行时错误: {e}")
output_result(ErrorCode.API_ERROR.value, str(e))
sys.exit(1)
except Exception as e:
logger.exception("未预期的错误")
output_result(ErrorCode.NETWORK_ERROR.value, "网络错误,请稍后重试")
sys.exit(1)
if __name__ == "__main__":
main()
这个实现的关键特点:
- 使用dataclass定义数据结构,提高代码可读性
- 完整的参数验证和错误处理
- 支持中英文城市名和温度单位转换
- 结构化的日志记录和结果输出
- 清晰的代码组织和模块划分
4.4 测试验证方法
完成开发后需要进行全面测试:
bash复制# 测试基本功能
python3 scripts/weather.py --city "北京"
# 测试英文城市
python3 scripts/weather.py --city "London" --days 3
# 测试华氏度
python3 scripts/weather.py --city "New York" --units imperial
# 测试错误处理
python3 scripts/weather.py --city ""
python3 scripts/weather.py --city "北京" --days 10
测试要点:
- 覆盖所有参数组合
- 验证边界条件
- 测试错误场景
- 检查日志输出
- 验证返回数据结构
5. 技能开发最佳实践总结
5.1 开发流程清单
| 阶段 | 关键任务 | 交付物 |
|---|---|---|
| 需求分析 | 用户画像、使用场景、功能边界 | 需求文档 |
| 架构设计 | 模块划分、接口定义、数据流 | 架构图 |
| SKILL.md编写 | 描述、参数、示例、响应格式 | SKILL.md |
| 脚本开发 | 参数解析、业务逻辑、错误处理 | Python脚本 |
| 测试验证 | 单元测试、集成测试、边界测试 | 测试报告 |
| 部署上线 | 目录部署、权限配置、日志配置 | 部署文档 |
5.2 常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| AI不调用技能 | 描述不够清晰 | 优化description,添加触发词 |
| 参数传递错误 | 类型不匹配 | 检查SKILL.md参数定义 |
| 脚本执行失败 | 依赖未安装 | 添加依赖说明,使用虚拟环境 |
| 返回结果混乱 | 输出格式不规范 | 统一使用JSON输出 |
| 日志丢失 | 日志配置错误 | 检查日志路径和权限 |
5.3 性能优化建议
- 缓存常用数据:对于频繁查询且变化不频繁的数据,可以添加缓存机制
- 批量处理请求:如果可能,将多个请求合并为一个批量请求
- 异步处理:对于耗时操作,考虑使用异步方式处理
- 连接池管理:重用HTTP连接,减少连接建立开销
- 压缩传输数据:对于大量数据,启用压缩减少传输时间
5.4 安全注意事项
- 参数过滤:对所有输入参数进行严格验证和过滤
- 敏感信息保护:不要在代码或日志中暴露API密钥等敏感信息
- 权限控制:确保技能只能访问必要的资源
- 速率限制:实现适当的速率限制,防止滥用
- 错误信息处理:避免在错误响应中暴露系统内部细节
6. 扩展与进阶
6.1 技能组合与工作流
OpenClaw支持技能组合,可以通过以下方式构建复杂工作流:
- 顺序执行:一个技能的输出作为下一个技能的输入
- 条件分支:根据前一个技能的结果决定执行哪个技能
- 并行执行:同时执行多个独立技能
- 错误处理:定义技能失败时的备用方案
6.2 性能监控与优化
为了确保技能的高性能运行,建议:
- 添加性能指标:记录关键操作的执行时间
- 实现健康检查:定期检查技能的健康状态
- 设置警报阈值:当性能指标超过阈值时触发警报
- 定期性能测试:模拟高负载场景,发现性能瓶颈
6.3 持续集成与部署
建立自动化的CI/CD流程:
- 自动化测试:每次提交自动运行测试套件
- 代码质量检查:使用工具检查代码风格和质量
- **自动化构建
