Skills 开发技巧 2:让大模型思考,让脚本执行

今天我集中开发了三个 Skills,分别是项目管理晨会汇总、站点证书监控与自动续签、博客自动发布。做完之后回头看,有一些开发模式上的心得值得总结——大模型负责思考,脚本负责执行,这是我在这个过程中反复验证的核心理念。

一、三个真实案例,三份收益

1. 项目管理晨会汇总(pm-scrum)

这个 Skill 对接 TAPD,每天早上自动拉取当前迭代的需求、任务、风险,生成结构化日报发到企微。

收益

以前每天晨会前要手动打开 TAPD,点进迭代看板,挨个确认哪些完成了、哪些逾期了、哪些没人认领,整理成消息发出去,10 分钟起步。现在一句话触发,30 秒出报告。

更重要的是,风险识别变系统化了。以前靠肉眼扫,容易漏;现在脚本按规则(R1~R7)遍历每一条需求和子任务,无负责人、无排期、逾期未完成,一条都跑不掉。今天实际跑下来,迭代2 的 11 个需求里发现了 11 条高风险 + 6 条中风险,这要靠人工发现整理,至少得看10分钟。

关键实现:迭代判断这里我踩了个坑——最初脚本直接取第一条 status=open 的迭代,结果拿到的是已经过期的迭代1(3月21日结束),数据完全错了。后来改成按日期范围判断:

def fetch_iteration(wid: str):
    today = date.today().isoformat()
    iters = tapd_list("iterations_get", {"workspace_id": wid, "status": "open", "limit": 20})

    # 过滤:今天必须在迭代的起止日期范围内
    active = [
        it for it in iters
        if it.get("startdate", "") <= today <= it.get("enddate", "")
    ]
    if active:
        active.sort(key=lambda x: x.get("startdate", ""), reverse=True)
        return active[0]

    # 无活跃迭代:返回特殊标记 + 候选列表,由 LLM 给出建议
    return {"_no_active": True, "candidates": [...]}

这段逻辑由 LLM 一次写好,之后每次执行结果完全确定,不再因为 LLM 判断方式的变化而漂移。


2. 站点证书监控与自动续签(site-health-monitor)

这个 Skill 每天定时检测 6 个站点的可用性和 SSL 证书有效期,发现证书快过期时自动 SSH 登录远程机器,用 certbot + dns-tencentcloud 插件续签,然后 nginx reload。

收益

今天跑了一次,发现 一个域名的证书只剩 5 天,已经触发了告警阈值(7天)。系统自动执行续签,整个过程:

检测到证书预警(5天)
  → SSH 登录 IP:Port
  → sudo -s → export 腾讯云密钥
  → certbot certonly -a dns-tencentcloud -d 域名 
  → nginx -t → nginx -s reload
  → 证书更新

全程无人工干预,我是在看日志的时候才知道这件事发生了。

关键实现:续签这个流程如果让 LLM 直接执行,每次 SSH 命令的拼法、certbot 参数的组合都可能有细微差别,一旦某个参数不对证书就续不上。把它固化成脚本:

def renew_domain(ssh, domain: str, secret_id: str, secret_key: str, dry_run=False):
    cmd = (
        f"sudo -s -- sh -c '"
        f"export TENCENTCLOUD_SECRET_ID={secret_id} "
        f"TENCENTCLOUD_SECRET_KEY={secret_key}; "
        f"certbot certonly -a dns-tencentcloud --agree-tos --non-interactive "
        f"-d {domain} -d www.{domain}'"
    )
    rc, stdout, stderr = ssh_exec(ssh, cmd, dry_run=dry_run)
    if rc != 0:
        raise RuntimeError(f"certbot 失败: {stderr}")
    return stdout

每次执行的命令完全一致,报错信息清晰,LLM 只需要读结果、判断成功还是失败。


3. 博客自动发布(blog-writer)

这个 Skill 负责博客从写作到上线的完整流程:生成文章骨架 → 写正文 → 生成 Banner 图 → Hugo 验证 → 提交 PR → 等待 merge → push Gitee → SSH 登录远程机器执行 make 更新博客 → Playwright 截图验证首页。

收益

以前发一篇博客:本地写 → hugo 验证 → git commit → push → 登服务器拉代码 → make → 打开浏览器看效果,每次 15-20 分钟,而且这么多步骤都需要在不断地切换工具来操作,繁琐麻烦,现在是可以一气呵成了。

现在整个流程跑下来是全自动的,我只需要:①确认文章内容 ②merge PR。其他的由 Skill 完成,包括最后的截图验证——部署完自动打开浏览器截图发给我,眼见为实。

关键实现:部署这步,服务器上的博客目录归 root 所有,ubuntu 用户没有写权限。如果让 LLM 决定怎么切换用户,每次可能用 sudo susudo -isudo -s 等不同方式,行为不稳定。固化到脚本:

SSH_USER = "ubuntu"
BLOG_DIR = "/data/www/blog"

def deploy(dry_run=False):
    rc, stdout, stderr = ssh_run(
        f"sudo -s -- sh -c 'cd {BLOG_DIR} && make 2>&1'",
        dry_run=dry_run
    )
    if rc != 0:
        raise RuntimeError(f"make 失败: {stderr}")
    return stdout

二、核心理念:为什么要「让脚本执行」

从上面三个案例可以看出两个共同点。

1. 不确定性收敛

大模型的输出是概率采样,每次都有细微差异。用同样的 prompt 让它生成 curl 命令,今天可能是 -H "Content-Type: application/json",明天可能多个空格或者参数顺序不同,大多数时候没问题,但在关键执行路径上,这种不确定性是隐患。

把执行逻辑写成脚本,LLM 的不确定性就被限制在「生成代码」这一步。代码一旦写好,每次执行路径完全确定:

LLM(不确定)→ 生成脚本(一次)→ 脚本执行(永远确定)

而不是:

LLM(不确定)→ 执行命令(每次都可能不同)

2. Token 消耗优化

脚本执行不消耗 Token,只有 LLM 处理文本才消耗。

以项目管理报告为例,如果让 LLM 直接用工具一条条拉 TAPD 数据、处理 JSON、过滤风险项,全程 LLM 参与,单次消耗 8000+ Token。

改成脚本后:脚本负责数据拉取和处理,LLM 只读最终结果生成报告,单次降到 2000 以内,降幅超过 75%。

数据处理、格式转换、条件判断,这些都是「确定性工作」,没有理由让 LLM 参与。

3. 只告诉大模型它不知道的

这是我在写 SKILL.md(技能说明文档)时总结出的另一个原则。

最开始我习惯在 SKILL.md 里写得很详细,把所有背景知识都加进去,结果文档越来越长,每次触发 Skill 都要把一大段已知知识塞进上下文,Token 消耗居高不下,LLM 反而容易在细枝末节上绕圈子。

后来我意识到:大模型对通用知识已经有很强的基础,不需要你教它怎么写 Python、怎么用 SSH、怎么解析 JSON。它真正需要的,是它无法凭通用知识推断的部分

  • 你的项目的具体路径(/data/www/blog
  • 你的服务器的端口(2330 而不是默认的 22
  • 你的业务规则(证书剩余 7 天触发告警,不是 30 天也不是 3 天)
  • 你踩过的坑(sudo -s 而不是 sudo su,因为目录归 root 所有)

这些是大模型无法从互联网训练数据里学到的,必须显式告诉它。而「Python 怎么读文件」、「certbot 的基本用法」这类通用知识,写进 SKILL.md 纯粹是浪费 Token。

一个简单的判断标准:如果这条信息在 Stack Overflow 上能搜到,就不用写;如果只存在于你的系统或你的决策里,就必须写。

这个原则用在 Skill 开发上,让我的 SKILL.md 从动辄 500 行压缩到 100 行以内,触发时的上下文更小,LLM 更聚焦,执行更准确。

反例:脱敏规则

今天有一个很典型的反例,印证了这个原则。

我在给 blog-writer Skill 增加「敏感信息脱敏」功能时,第一反应是把所有脱敏类型整理成一张详细的表格写进 SKILL.md——IP 地址用 <YOUR_IP> 替换、Token 用 <API_KEY> 替换、数据库连接串用 <DSN> 替换……洋洋洒洒列了十几行。

写完之后想了想:这些信息大模型早就知道。IP、Token、密码、API Key 是敏感信息,是业界常识,Stack Overflow 上随便一篇安全文章都会提到。我把它写进 SKILL.md,不过是在用大模型自己的知识教大模型,纯属浪费 Token。

最终版本是一句话:

生成博客正文时,对所有敏感信息进行脱敏处理,替换为语义明确的占位符。

大模型看到这句话,凭通用知识就能正确执行——该脱敏什么、怎么替换,它都清楚。十几行表格能做到的事,一句话就够了。

写 SKILL.md 的核心原则:只写大模型不知道的(你的系统配置、你的业务规则、你踩过的坑),其余的信任它的通用能力。


三、执行循环:自愈式开发模式

基于这个理念,我现在写 Skill 的标准流程是:

Task      明确目标
  ↓
Plan      LLM 规划步骤,设计数据流
  ↓
Code      LLM 生成 Python 脚本
  ↓
Execute   脚本执行,返回结果或报错
  ↓
Feedback  LLM 分析输出,报错则定位修复,重回 Code

Feedback 这步是关键。脚本非零退出不是终点,而是 LLM 的信号:「出错了,看 stderr 修。」这个自愈循环让 Skill 在遇到环境变化时能自己恢复,而不是崩掉让人工干预。

def main():
    try:
        ctx = plan(args)
        result = execute(ctx)
        print(result)       # LLM 读取此输出做 Feedback
        sys.exit(0)
    except Exception as e:
        print(f"ERROR: {e}", file=sys.stderr)
        sys.exit(1)         # 非零退出码 → 触发 LLM 自愈循环

四、总结

三个 Skill 开发下来,这套模式给我的最直接感受:

  • :关键路径行为确定,不会因为 LLM 今天"心情不好"而执行出不同结果
  • :Token 消耗降了一半以上,对话更快,成本更低
  • 好调试:脚本可以独立运行,加日志、加单测,不用把整个 Agent 跑起来验证一个逻辑

大模型的脑子很灵,但需要一双确定的手。脚本就是那双手。

在 AI Agent 开发中,这个分工越清晰,系统越健壮。