OpenClaw Skill 扩展机制详解 — 以 AWS 成本监控为例的开发实践

一、问题背景

我在用 OpenClaw 做日常自动化时遇到一个需求:每天查一次亚马逊云科技的账单,按服务拆分,超预算就告警。

OpenClaw 本身没这个能力。但它有一套叫 Skill 的扩展机制,允许通过 Markdown 文件给 AI Agent 添加新技能。折腾了一下午搞明白了整个机制,顺便写了个成本监控 Skill。

这篇把理解和实践系统整理一下。


二、Skill 机制概述

2.1 基本概念

Skill 是 OpenClaw 中 Agent 的能力扩展单元。不是代码插件,而是一份 Markdown 格式的操作手册。AI Agent 读取文档中的指令后,自行调用底层工具完成任务。

2.2 执行流程

用户请求 → Agent 扫描 Skill 描述 → 语义匹配 → 读取 SKILL.md → 按指令执行

2.3 三层加载设计

层级 内容 加载时机
元数据层 Description 启动时常驻
指令层 SKILL.md 正文 触发时加载
资源层 scripts/、references/ 引用时加载

渐进式加载是为了管理上下文窗口。只有当前需要的内容才占用空间。


三、目录结构与编写规范

3.1 标准布局

~/.openclaw/workspace/skills/aws-cost-monitor/
├── SKILL.md              # 必需:技能定义
├── scripts/
│   └── check_cost.py     # 可选:辅助脚本
└── references/
    └── cost-explorer-notes.md  # 可选:参考文档

3.2 SKILL.md 结构

# Skill 名称

## Description
功能描述

## Triggers
- 触发条件列表

## Steps
1. 执行步骤

## Notes
- 注意事项

## References
- 参考文档路径

3.3 Triggers 编写要点

这是我踩坑的地方。一开始只写了书面语,用户口语问就匹配不上。比如"查 AWS 账单"能触发,"这个月云上花了多少"就不行。

经验:先上线基础版本,实际对话中观察哪些说法没覆盖到,迭代补充。


四、实战:AWS 成本监控 Skill

4.1 SKILL.md

# AWS Cost Monitor

## Description

亚马逊云科技成本监控技能。查询云资源费用,
生成报告,按服务拆分,超预算告警。

## Triggers

- 用户说"查一下我的 AWS 账单"
- 用户说"这个月花了多少钱"
- 用户说"AWS 成本报告"
- 用户说"看看云资源开销"
- 用户说"账单超了没"
- 用户说"成本分析"
- 用户说"哪个服务花钱多"

## Steps

1. 执行 scripts/check_cost.py 获取成本数据
2. 解析 JSON 输出
3. 生成报告:

亚马逊云科技成本报告 | YYYY-MM-DD
统计周期:YYYY-MM-01 ~ YYYY-MM-DD
本月累计费用:$XXX.XX
按服务拆分(Top 5)
日均费用 /  预估月底总费用

4. 预估超 $500 则追加告警
5. 用户指定阈值则替代默认值

## Notes

- 需要 AWS CLI 凭证和 ce:GetCostAndUsage 权限
- 数据有 24-48 小时延迟
- Cost Explorer API 只能通过 us-east-1 调用

4.2 scripts/check_cost.py

#!/usr/bin/env python3
"""
AWS Cost Monitor — 查询亚马逊云科技账户成本数据
依赖:boto3  权限:ce:GetCostAndUsage
"""

import json
import sys
from datetime import datetime, timedelta

import boto3


def get_month_range(year: int = 0, month: int = 0) -> tuple[str, str]:
    """获取指定月份的起止日期。"""
    today = datetime.utcnow()
    if year == 0 or month == 0:
        year = today.year
        month = today.month

    start = f"{year:04d}-{month:02d}-01"

    if month == 12:
        end_date = datetime(year + 1, 1, 1)
    else:
        end_date = datetime(year, month + 1, 1)

    # 当前月 End 用明天(排他的)
    if year == today.year and month == today.month:
        end_date = today + timedelta(days=1)

    return start, end_date.strftime("%Y-%m-%d")


def query_total_cost(client, start: str, end: str) -> float:
    """查询总费用。"""
    resp = client.get_cost_and_usage(
        TimePeriod={"Start": start, "End": end},
        Granularity="MONTHLY",
        Metrics=["UnblendedCost"],
    )
    total = 0.0
    for result in resp["ResultsByTime"]:
        total += float(result["Total"]["UnblendedCost"]["Amount"])
    return round(total, 2)


def query_cost_by_service(client, start: str, end: str) -> list[dict]:
    """按服务维度查询费用。"""
    resp = client.get_cost_and_usage(
        TimePeriod={"Start": start, "End": end},
        Granularity="MONTHLY",
        Metrics=["UnblendedCost"],
        GroupBy=[{"Type": "DIMENSION", "Key": "SERVICE"}],
    )
    services = {}
    for result in resp["ResultsByTime"]:
        for group in result["Groups"]:
            name = group["Keys"][0]
            amount = float(group["Metrics"]["UnblendedCost"]["Amount"])
            services[name] = services.get(name, 0.0) + amount

    sorted_list = sorted(services.items(), key=lambda x: x[1], reverse=True)
    return [
        {"service": n, "amount": round(a, 2)}
        for n, a in sorted_list if a > 0.01
    ]


def estimate_monthly_total(total_so_far: float, start: str) -> float:
    """预估月底总费用。"""
    today = datetime.utcnow()
    start_date = datetime.strptime(start, "%Y-%m-%d")
    days_passed = (today - start_date).days
    if days_passed <= 0:
        return total_so_far

    if start_date.month == 12:
        next_month = datetime(start_date.year + 1, 1, 1)
    else:
        next_month = datetime(start_date.year, start_date.month + 1, 1)

    total_days = (next_month - start_date).days
    return round(total_so_far / days_passed * total_days, 2)


def main():
    year, month = 0, 0
    if len(sys.argv) >= 3:
        year, month = int(sys.argv[1]), int(sys.argv[2])

    start, end = get_month_range(year, month)
    client = boto3.client("ce", region_name="us-east-1")

    total = query_total_cost(client, start, end)
    services = query_cost_by_service(client, start, end)
    today = datetime.utcnow()
    days_passed = (today - datetime.strptime(start, "%Y-%m-%d")).days

    result = {
        "period": {"start": start, "end": end},
        "total_cost": total,
        "daily_average": round(total / max(days_passed, 1), 2),
        "estimated_monthly_total": estimate_monthly_total(total, start),
        "days_passed": days_passed,
        "top_services": services[:10],
        "query_time": today.strftime("%Y-%m-%d %H:%M:%S UTC"),
    }
    print(json.dumps(result, indent=2, ensure_ascii=False))


if __name__ == "__main__":
    main()

4.3 references/cost-explorer-notes.md

# Cost Explorer API 注意事项

## 权限要求

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["ce:GetCostAndUsage", "ce:GetCostForecast"],
      "Resource": "*"
    }
  ]
}

关键限制

  1. API 只能通过 us-east-1 调用
  2. 数据延迟 24-48 小时
  3. TimePeriod.End 不包含当天
  4. 返回金额单位为 USD

---

## 五、安装与测试

```bash
# 创建目录
mkdir -p ~/.openclaw/workspace/skills/aws-cost-monitor/scripts
mkdir -p ~/.openclaw/workspace/skills/aws-cost-monitor/references

# 放文件后加执行权限
chmod +x ~/.openclaw/workspace/skills/aws-cost-monitor/scripts/check_cost.py

# 确认依赖
pip install boto3
aws sts get-caller-identity

独立测试脚本:

python3 ~/.openclaw/workspace/skills/aws-cost-monitor/scripts/check_cost.py

预期输出一段 JSON 费用数据。

在 OpenClaw 中测试: 文件放好后不用重启,说"查一下我的 AWS 账单"即可触发。


六、调试经验

Cost Explorer API 的坑

Region 限制:只认 us-east-1,和资源所在 Region 无关。折腾了二十分钟才搞明白。

End 日期排他:查到今天需要 End 写明天。脚本里已处理。

数据延迟:新账户查出来全是零,等 24-48 小时就好。

Skill 调试

  • 问 Agent"你有哪些 skills"确认是否已加载
  • 没触发多半是 Triggers 覆盖不全,补充口语变体
  • 脚本报错检查:boto3 是否安装、AWS 凭证是否配好、IAM 权限是否足够

七、进阶扩展

定时巡检

HEARTBEAT.md 中添加:

- [ ] 每天早上 9:00(UTC+8),执行 AWS 成本监控

Skill 嵌套

Steps 里引用其他 Skill,Agent 自动串联执行:

6. 费用超阈值时,使用"飞书通知"Skill 发送告警

与 Amazon Bedrock 配合

把成本数据丢给 Amazon Bedrock 分析趋势和异常:

7. 将成本数据发给 Amazon Bedrock,分析趋势并给出优化建议

实际用了一段时间,不光看数字,还能拿到"EC2 费用比上月涨了 35%,建议检查闲置实例"这种分析。

条件触发

根据日期调整报告详细程度:

  • 月初到月中:简要报告
  • 月中到月底:完整报告含趋势分析
  • 月底最后一天:月度总结

八、发布到 ClawHub

写好的 Skill 可以发布到 ClawHub 让其他人使用:

  1. SKILL.md 描述完整
  2. 不硬编码敏感信息
  3. 打包提交

九、总结

步骤 内容 耗时
需求分析 确定做成本监控 5 分钟
目录规划 SKILL.md + scripts/ + references/ 2 分钟
编写 SKILL.md Triggers + Steps 15 分钟
编写脚本 check_cost.py 20 分钟
安装调试 放文件 + 测试 15 分钟

不到一小时。不需要搭环境、不需要编译。写好 Markdown 和脚本放到目录下就生效。

Skill 的核心价值在于降低 Agent 能力扩展的门槛——把"做什么"和"怎么做"用自然语言写清楚,剩下的交给 Agent。

有问题欢迎评论交流。

posted @ 2026-03-20 12:06  亚马逊云开发者  阅读(9)  评论(0)    收藏  举报