在 AI 热点监控工具里,把「抓热点、做分析、判风险、出摘要」拆成一组可独立加载的 Skill,比把整段提示词写死在系统提示里更稳。每个 Skill 自带说明、参考模板与确定性脚本,模型只在命中对应环节时才加载,上下文占用小、行为可复现。下文以该项目的实际目录划分与加载链路,讲清封装怎么落。
一、为什么在监控工具里用 Skills
热点监控是一条多环节流水线:先轮询各平台拉取候选话题,再让大模型判断是否为真热点,接着做内容分析与风险分级,随后生成摘要推前端。把这些环节全塞进一条长提示,模型容易在靠后环节「忘记」前面的约束。按环节拆成 Skill,每个只管一件事,加载即用、用完即走。
| 环节 | 对应 Skill | 交付物 |
|---|---|---|
| 候选抓取 | fetch-hotspot | 平台原始话题列表 |
| 热点判定 | judge-trending | 是否越线 + 置信度 |
| 内容分析 | analyze-content | 实体、情绪、来源 |
| 风险分级 | grade-risk | high / mid / low |
| 摘要生成 | summary | 三段式结构化摘要 |
二、项目的 Skill 目录划分
遵循开放标准的基础结构,每个 Skill 一个目录,根文件是 SKILL.md。确定性计算放进 scripts/,大段参考规则放进 reference.md,避免主文件被细节撑爆。这套划分直接对齐监控流水线的五个环节,新增一类分析(如情绪走向预测)只需加一个目录,不改动既有 Skill 与调度主干,扩展成本是常数级。每个目录自描述、自携带,调度层扫一遍就能发现并接入,新人也能在一个文件夹内看懂一个环节的完整契约,不必翻遍整条长提示。
skills/
fetch-hotspot/
SKILL.md
judge-trending/
SKILL.md
reference.md
analyze-content/
SKILL.md
scripts/extract.py
grade-risk/
SKILL.md
scripts/score.py
resources/thresholds.json
summary/
SKILL.md
resources/prompt.json
grade-risk 这种带阈值的环节,把临界值抽到 resources/thresholds.json,调参不必改提示词;score.py 做确定性打分,结果稳定、不随每次生成漂移。
三、封装落地的五步
- 为每个环节写
SKILL.md,description用「当……时调用」句式,便于模型语义匹配; - 把长规则拆到
reference.md,主文件只留步骤与入口,控制单文件 token; - 凡是排序、打分、字段抽取,写成
scripts/里的脚本,由模型调用而非生成; - 阈值与模板抽到
resources/,业务变更改数据文件、不动提示词; - 在调度层维护技能清单,按流水线顺序或模型自决触发,逐级加载。
3.1 调度层如何逐级加载
下面这段 Python 给出「先读元信息、命中再读全文、需要时跑脚本」的简化加载器,对应渐进式披露的四级。
import json, importlib.util, os
class SkillLoader:
def __init__(self, root: str):
self.root = root
self.index = self._scan()
def _scan(self):
# 首级:只加载 name + description 做匹配
idx = {}
for name in os.listdir(self.root):
md = os.path.join(self.root, name, "SKILL.md")
if os.path.isfile(md):
meta = self._frontmatter(md)
idx[name] = meta
return idx
def match(self, task: str):
return [n for n, m in self.index.items() if task in m.get("description", "")]
def run(self, name: str, payload: dict):
# 第二级:读取 SKILL.md 全文并执行脚本
skill_dir = os.path.join(self.root, name)
script = os.path.join(skill_dir, "scripts", "main.py")
if os.path.isfile(script):
spec = importlib.util.spec_from_file_location("m", script)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod.execute(payload)
return {"skill": name, "payload": payload}
def _frontmatter(self, path: str) -> dict:
text = open(path, encoding="utf-8").read()
block = text.split("---")[1] if text.startswith("---") else ""
return json.loads("{" + block + "}")
四、封装带来的实际收益
环节隔离让单个 Skill 可单独迭代:grade-risk 改阈值只动 thresholds.json,不影响摘要风格;某个 Skill 调试失败,也不会把整条流水线带崩。确定性脚本把「打分」从概率生成变成固定函数,同一话题多次跑结果一致,前端推送才可信。
跨平台也是红利。按开放标准写的 Skill 文件夹,既能在本项目跑,也能直接挪到支持该标准的其他智能体环境,不必为换平台重写提示词。
调试体验跟着改善。内联提示时期,一处报错要在整段长文本里肉眼定位;拆成 Skill 后,问题被圈在某个目录内,读 SKILL.md 就能还原该环节的完整预期,配合自带脚本的独立单测,定位从分钟级降到读一个文件。团队并行也不再互相踩脚:两人各改 analyze-content 与 summary,目录隔离让合并冲突几乎只发生在各自资源文件,主干调度层始终不动。
这种「能力即目录、目录即可测、可并行」的属性,正是模块化封装在中等规模项目里站得住脚的理由。它没引入插件平台那套中心化注册与生命周期治理,却拿到了拆分带来的绝大多数好处——改动范围可控、回归面小、新人能在一个目录内看懂一个环节的完整契约。
五、封装时的两个坑
把脚本路径写死在提示词里是反模式——一旦目录变动就失联。正确做法是 Skill 内用相对路径自描述,调度层按约定目录解析。另一个坑是 SKILL.md 写太长,模型加载即吞掉大块上下文。主文件只放「做什么、按什么顺序」,细节下沉到 reference.md 与 resources/,命中深层需求再拉取。
六、触发方式:编排还是自决
调度层怎么决定「现在跑哪个 Skill」,项目里走了两条路,按环节性质选。
流水线前端环节适合显式编排。抓取、判定、分析是固定顺序,调度器按数组依次加载,出错能精确定位到第几步,也方便做超时与重试。后端环节适合模型自决。摘要风格、风险口径这类带主观判断的,把候选 Skill 的 description 全交给模型,由它根据当前话题挑合适的那个,灵活度更高。
| 触发方式 | 适合环节 | 优点 | 风险 |
|---|---|---|---|
| 顺序编排 | 抓取→判定→分析 | 可控、易排查 | 不够灵活 |
| 模型自决 | 摘要、分级 | 适配多样输入 | 可能选错 |
实践里用「编排打底、自决补位」:主干用编排保证稳定,关键分支放开给模型挑 Skill。两种触发共享同一套加载器,只是入口不同,不增加额外维护面。
常见问题(FAQ)
Q1:每个环节都要建独立 Skill 吗?
按职责拆分即可,相关小步可合并进一个 Skill 减开销。
Q2:脚本放 Skill 里安全吗?
需可信来源。项目内脚本应走沙箱或受限执行环境。
Q3:改阈值要动提示词吗?
不用。阈值抽到 resources 数据文件,改数据即可。