针对 DeepSeek Harness 修改。上游:Anthropic 的 skill-creator(Apache-2.0,见 LICENSE.txt)。
技能创建器
一个用于创建新技能并迭代改进它们的技能。
在高层面上,创建技能的过程如下:
- 确定技能要做什么以及大致如何做
- 编写技能草稿
- 创建几个测试提示,并在安装技能的情况下运行模型
- 帮助用户定性和定量评估结果
- 在后台运行的同时,如果没有定量评估,则草拟一些;如果已有,可以直接使用或根据需要进行修改。然后向用户解释它们(或者如果已有,解释已有的那些)
- 使用
eval-viewer/generate_review.py脚本向用户展示结果供其查看,并让他们查看定量指标
- 根据用户对结果的评估反馈重写技能(如果定量基准暴露出明显缺陷,也应修改)
- 重复直到满意
- 扩展测试集并以更大规模重新尝试
使用此技能时,您需要判断用户处于这个过程的哪个阶段,然后跳入并帮助他们推进这些阶段。例如,可能用户说“我想为X制作一个技能”。您可以帮助缩小他们的需求范围,编写草稿、测试用例,确定评估方式,运行所有提示,并重复。
另一方面,如果用户已经有技能草稿,您可以直接进入评估/迭代循环部分。
当然,您应该始终保持灵活,如果用户说“我不需要运行一堆评估,就靠感觉来”,您也可以这样做。
技能完成后(但顺序可以灵活),您还可以运行技能描述改进器(我们为此准备了单独的脚本),以优化技能的触发。
酷吧?酷。
与用户沟通
技能创建者可能被不同熟悉程度的人使用。如果您还没听说(怎么可能,这还是最近才开始的),现在有个趋势:DSH 的力量激励水管工打开他们的终端,父母和祖父母搜索“如何安装 npm”。另一方面,大多数用户可能相当懂计算机。
所以请注意上下文线索,以理解如何组织您的沟通!在默认情况下,供您参考:
- “评估”和“基准”处于边界,但可以接受
- 对于“JSON”和“断言”,您需要看到用户明显知道这些是什么,才能无需解释直接使用
如果不确定用户是否能理解,可以简短解释术语,并随时用简短定义澄清术语。
创建技能
捕捉意图
首先理解用户的意图。当前对话可能已经包含用户想要捕捉的工作流(例如,他们说“把这个变成技能”)。如果是这样,首先从对话历史中提取答案——使用的工具、步骤顺序、用户做出的修正、观察到的输入/输出格式。用户可能需要填补空白,并应在进入下一步前确认。
- 这个技能应该让 DSH 能做什么?
- 技能应在何时触发?(用户的哪些短语/上下文)
- 预期输出格式是什么?
- 是否应设置测试用例来验证技能?具有客观可验证输出的技能(文件转换、数据提取、代码生成、固定工作流步骤)受益于测试用例。主观输出的技能(写作风格、艺术)通常不需要。根据技能类型建议适当的默认值,但让用户决定。
访谈与研究
主动询问关于边界情况、输入/输出格式、示例文件、成功标准和依赖项的问题。等到这部分完善后再编写测试提示。
检查可用的 MCP - 如果对研究有用(搜索文档、查找类似技能、查找最佳实践),可以通过子代理并行研究(如果可用),否则内联进行。带着上下文准备,以减少用户负担。
编写 SKILL.md
根据用户访谈,填写以下组件:
- name:技能标识符
- description:何时触发、它做什么。这是主要的触发机制——包括技能做什么以及何时使用的具体上下文。所有“何时使用”的信息都放在这里,而不是正文中。注意:目前模型有“低触发”的趋势——即有用时却不使用技能。为了解决这个问题,请让技能描述稍微“强势”一点。例如,不要写“如何构建一个简单的快速仪表板来显示内部公司数据”,而应该写“如何构建一个简单的快速仪表板来显示内部公司数据。确保每当用户提到仪表板、数据可视化、内部指标或希望显示任何类型的公司数据时都使用此技能,即使他们没有明确要求‘仪表板’。”
- whenToUse / metadata / disable-model-invocation / user-invocable:DSH 识别的可选前置内容键。DSH 从前置内容读取
name、description、whenToUse、metadata、disable-model-invocation和user-invocable;您想记录的其他内容(如许可证、版本)放在metadata下,而不是作为顶级键。 - **技能的其余部分 :) **
技能编写指南
技能的解剖结构
skill-name/
├── SKILL.md(必需)
│ ├── YAML 前置内容(name、description 必需)
│ └── Markdown 指令
└── 捆绑资源(可选)
├── scripts/ - 确定性/重复性任务的可执行代码
├── references/ - 根据需要加载到上下文的文档
└── assets/ - 输出中使用的文件(模板、图标、字体)
渐进式披露
技能使用三级加载系统:
- 元数据(name + description)——始终在上下文中(约100词)
- SKILL.md 正文——技能触发时在上下文中(理想情况小于500行)
- 捆绑资源——按需使用(无限制,脚本可以在不加载的情况下执行)
这些词数是大致的,如果需要可以更长。
关键模式:
- 保持 SKILL.md 在500行以下;如果接近这个限制,添加额外的层次结构以及清晰的指示,告诉使用技能的模型下一步该去哪里跟进。
- 在 SKILL.md 中清晰地引用文件,并附上何时阅读的指导
- 对于大参考文件(超过300行),包含一个目录
领域组织:当技能支持多个领域/框架时,按变体组织:
cloud-deploy/
├── SKILL.md(工作流 + 选择)
└── references/
├── aws.md
├── gcp.md
└── azure.md
DSH 只读取相关的参考文件。
无意外原则
不用说,技能不得包含恶意软件、漏洞代码或任何可能危及系统安全的内容。技能的内容在描述其意图时不应让用户感到意外。不要同意创建误导性技能或旨在促进未经授权访问、数据泄露或其他恶意活动的请求。像“角色扮演成 XYZ”这样的内容是可以的。
编写模式
在指令中优先使用祈使句。
定义输出格式 - 可以这样做:
## 报告结构
始终使用此确切模板:
# [标题]
## 执行摘要
## 主要发现
## 建议
示例模式 - 包含示例很有用。可以这样格式化它们(但如果示例中有“输入”和“输出”,可能需要稍微偏离):
## 提交消息格式
**示例 1:**
输入:使用 JWT 令牌添加用户认证
输出:feat(auth): implement JWT-based authentication
写作风格
尝试向模型解释为什么事情重要,而不是使用严厉的必须。使用心理理论,并尝试使技能通用化,不要过于狭窄地针对特定示例。首先编写草稿,然后以新鲜的眼光审视并改进它。
测试用例
编写技能草稿后,提出2-3个现实的测试提示——真实用户可能会说的那种。与用户分享:[您不必使用确切的语言]“这里有几个我想尝试的测试用例。这些看起来对吗,或者您想添加更多?”然后运行它们。
将测试用例保存到 evals/evals.json。暂时不要编写断言——只需提示。您将在下一步中在运行进行时草拟断言。
{
"skill_name": "example-skill",
"evals": [
{
"id": 1,
"prompt": "User's task prompt",
"expected_output": "Description of expected result",
"files": []
}
]
}
参见 references/schemas.md 获取完整 schema(包括 assertions 字段,您稍后会添加)。
运行和评估测试用例
本节是一个连续序列——不要中途停止。不要使用 /skill-test 或其他测试技能。
将结果放在 <skill-name>-workspace/ 中,作为技能目录的兄弟目录。在工作空间中,按迭代组织结果(iteration-1/、iteration-2/ 等),在每个迭代中,每个测试用例获得一个目录(eval-0/、eval-1/ 等)。不要预先创建所有这些——只需在需要时创建目录。
步骤 1:在同一轮中启动所有运行(带技能和基线)
对于每个测试用例,在同一轮中启动两个子代理——一个带技能,一个不带。这很重要:不要先启动带技能运行,然后再回来看基线。一次性启动所有运行,以便它们大约同时完成。
带技能运行:
执行此任务:
- 技能路径:<path-to-skill>
- 任务:<eval prompt>
- 输入文件:<eval files(如果有),或 "none">
- 将输出保存到:<workspace>/iteration-<N>/eval-<ID>/with_skill/outputs/
- 要保存的输出:<用户关心的内容——例如,“.docx 文件”、“最终的 CSV”>
基线运行(相同提示,但基线取决于上下文):
- 创建新技能:完全没有技能。相同提示,无技能路径,保存到
without_skill/outputs/。 - 改进现有技能:旧版本。在编辑之前,通过
shutil.copytree复制技能目录(例如<workspace>/skill-snapshot/来自<skill-path>),然后将基线子代理指向快照。保存到old_skill/outputs/。
为每个测试用例编写一个 eval_metadata.json(断言暂时可以为空)。根据测试内容给每个评估起一个描述性名称——不仅仅是“eval-0”。在目录中也使用此名称。如果此迭代使用新的或修改的评估提示,为每个新的评估目录创建这些文件——不要假设它们从前面的迭代继承。
{
"eval_id": 0,
"eval_name": "descriptive-name-here",
"prompt": "The user's task prompt",
"assertions": []
}
步骤 2:在运行进行时,草拟断言
不要只是等待运行完成——您可以利用这段时间高效工作。为每个测试用例草拟定量断言并向用户解释。如果 evals/evals.json 中已有断言,请审查它们并解释它们检查什么。
好的断言是客观可验证的,并且有描述性名称——它们在基准查看器中应该清晰可读,以便浏览结果的人立即理解每个断言检查什么。主观技能(写作风格、设计质量)最好定性评估——不要将断言强加于需要人类判断的内容。
草拟好断言后,更新 eval_metadata.json 文件和 evals/evals.json。还要向用户解释他们在查看器中会看到什么——包括定性输出和定量基准。
步骤 3:当运行完成时,捕获时间数据
当每个子代理任务完成时,您会收到通知,但它既不携带 token 计数也不携带经过时间。自己从墙钟获取持续时间,并从子会话的记录中读取 token 总数,记录位于 ~/.dsh/storages/session_projcache/sessions/<childId>.json(tokenUsage.val.totals——uncachedInputTokens、outputTokens、cacheReadTokens、cacheWriteTokens)。立即将此数据保存到运行目录中的 timing.json:
{
"total_tokens": 84852,
"duration_ms": 23332,
"total_duration_seconds": 23.3
}
如果该会话记录缺失或没有 token 使用数据,则省略 token 字段,而不是编造数字。处理每个到达的通知,而不是尝试批量处理。
步骤 4:评分、汇总并启动查看器
当所有运行完成后:
-
评分每个运行——启动一个评分子代理(或内联评分),读取
agents/grader.md并评估每个断言与输出。将结果保存到每个运行目录中的grading.json。grading.json 的 expectations 数组必须使用字段text、passed和evidence(而不是name/met/details或其他变体)——查看器依赖于这些确切的字段名。对于可以通过编程检查的断言,编写并运行脚本而不是肉眼检查——脚本更快、更可靠,并且可以在迭代之间重用。 -
汇总为基准——从 dsh-skill-creator 目录运行汇总脚本:
python -m scripts.aggregate_benchmark <workspace>/iteration-N --skill-name <name>这会生成
benchmark.json和benchmark.md,包含通过率、时间和 token 等信息。
…[截断]