拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Unreal Agent 技能机制深度解析:SkillUse 工具与 SKILL.md 加载协议

Unreal Agent 技能机制深度解析:SkillUse 工具与 SKILL.md 加载协议

【免费下载链接】unreal-agent

Async-first agent harness

项目地址:https://gitcode.com/gh_mirrors/un/unreal-agent
点击查看免费下载

导读

本文聚焦 Unreal Agent Harness(项目目录unreal-agent)中的"技能(Skill)"体系:当模型需要处理特定任务时,如何通过SkillUse工具加载一份SKILL.md指令文件,以及技能文件内的相对路径如何被解析为绝对路径后再交给底层工具。文章以 skill-preamble.md 的三条核心指令为骨架,结合 skills.go、skill_use.go 与 skill_use.go 的源码实现,还原技能从注册、注入系统提示词、被模型调用、到异步读取文件并回传结果的完整链路。读完本文,你将掌握在 Unreal Agent 中注册与调用技能的正确姿势、SKILL.md内部相对路径的解析规则,以及该机制与异步工具调用模型的关系。

技能前导(Skill Preamble)的三条核心指令

skill-preamble.md 是一段极简但语义明确的模型指令文本,全文只有三条规则:

  1. 技能的本质:以下技能(skills)为特定任务提供专门的指令(specialized instructions for specific tasks)。技能不是可执行代码,而是"指令文件",作用是告诉模型"遇到这类任务时,应该遵循哪套流程/规范"。
  2. 用 SkillUse 加载:当任务与某个技能的描述(description)匹配时,应当使用SkillUse工具加载该技能对应的文件。也就是说,技能是按需懒加载的——技能文件的内容不会预先全部塞进上下文,而是由模型在判断"当前任务需要它"之后,通过工具调用动态读取。
  3. 相对路径解析规则:当技能文件内部引用了相对路径时,必须以技能目录为基准解析——即SKILL.md所在目录的父目录(等价于该路径的 dirname)——并将解析后的绝对路径用于后续工具调用。这一条是技能体系与工作区文件系统衔接的关键约定。

这条 preamble 不是写给人看的文档,而是随每个模型请求一起发送的系统提示词的一部分(通过//go:embed编译进二进制,见 skills.go),其作用是在每一轮对话开始前就为模型建立"技能调用协议"的心智模型。

技能注册与系统提示词的动态拼接

Skill 的三元组结构

在工具层,一个技能被建模为Name + Description + Path三元组,见 tool.go:

type Skill struct { Name string Description string Path string }
  • Name:技能的唯一标识,模型在SkillUse参数中引用它;
  • Description:技能用途描述,模型据此判断"当前任务是否匹配该技能";
  • Path:技能指令文件的路径(通常指向该技能目录下的SKILL.md)。

注册机制与提示词注入

技能的注册由Registry接口管理(tool.go),提供RegisterSkill、UnregisterSkill、Skills()等方法,说明技能可以在运行时动态注册/注销,而不是静态写死的工具。

当构建模型请求时,builder.go 的NewBuilder(skills ...tool.Skill)会把已注册技能清单序列化后拼接到系统提示词中:

func NewBuilder(skills ...tool.Skill) Builder { currentPreamble := preamble if skillPrompt := formatSkillsForPrompt(skills); skillPrompt != "" { currentPreamble += "\n\n" + skillPrompt } ... }

XML 技能清单的生成

skills.go 中的formatSkillsForPrompt负责把技能列表编码为 XML 片段,追加在 skill-preamble 之后:

func formatSkillsForPrompt(skills []tool.Skill) string { if len(skills) == 0 { return "" } ... encoded, err := xml.Marshal(availableSkills{Skills: promptSkills}) ... return skillPreamble + "\n\n" + string(encoded) }

生成格式如下(以 builder_test.go 的测试快照为例):

<available_skills> <skill> <name>go-review</name> <description>Review &lt;Go&gt; &amp; "tests"</description> <location>/skills/reviewer's/SKILL.md</location> </skill> <skill> <name>documents</name> <description>Edit documents</description> <location>/skills/documents/SKILL.md</location> </skill> </available_skills>

注意三个细节:

  • XML 中技能描述里的特殊字符(<、&、")会被自动转义,保证注入提示词后模型仍然能正确解析;
  • location字段携带的是技能的完整文件路径(通常以SKILL.md结尾),它是Skill三元组中Path字段的直传;
  • 若没有任何已注册技能,formatSkillsForPrompt返回空串,preamble 中不会出现<available_skills>段。

由此,模型在每轮请求中都能"看到"当前可用的技能清单(名称、用途、文件位置),并结合 skill-preamble 的规则决定何时调用SkillUse。

SkillUse 工具:加载已注册技能的指令

工具定义

SkillUse是 Unreal Agent 的三个内置静态工具之一(与Bash、ViewImage并列,见 registry.go),其模型可见定义位于 static.go:

  • 名称:SkillUse
  • 描述:Load the instructions for a registered skill.(加载一个已注册技能的指令)
  • 参数:一个必填字符串参数name,即"要加载的技能的确切名称"(the exact name of the skill to load)。

从参数设计可以看出,模型侧只需传递技能名,文件路径完全由 harness 内部解析,模型无需也不应猜测磁盘布局。

参数校验与技能解析

模型发起的工具调用由 skill_use.go 中的skillUseTranslator.Translate处理,其校验链如下:

  1. 调用名必须与静态工具名SkillUse一致,否则报错skill-use call name ... does not match static tool "SkillUse";
  2. 参数为空时按{}处理(容错设计),随后解出{"name": "..."}结构;
  3. name为空时报错skill-use argument "name" must be set;
  4. 在注册表中按名称解析技能,未命中时报错skill "..." is not registered;
  5. 命中后,用技能文件的Path构造一个 skill-use 操作(operation.NewSkillUseSpec(skill.Path)),提交到协调器并返回WaitingFor状态——注意这里工具调用立即返回,真正的文件读取是异步进行的,这与该 harness 的整体 async-first 架构完全一致。

未命中技能的容错

若模型请求了未注册的技能名,调用不会崩溃,而是把错误文本直接作为工具结果返回给模型(TranslateResult中status.Error != ""分支,见 skill_use.go),让模型在下一轮自行纠正。这正是技能清单需要被注入提示词的原因:模型看到<available_skills>就知道该用哪个名字。

SkillUse 操作:底层异步读取状态机

SkillUse在操作层被建模为一个可持久化的 operation(TypeSkillUse/Version 1),完整状态机位于 skill_use.go:

type SkillUseState struct { Path string Content []byte TerminalError string }

其状态流转为Ready → Awaiting → Completed / Failed / Canceled(skill_use.go):

  • Ready:首次推进时派发一个IOReadprimitive,读取SKILL.md的全部内容(Count: math.MaxInt64,即一次读完,见 skill_use.go),并把操作置为Awaiting;
  • Awaiting:等待底层 IO 事件回流。每个IOReadOutput事件都会把读取到的数据块追加到Content,直到收到IOReadCompleted事件——此时校验累计字节数与Content长度一致后,操作进入Completed(skill_use.go);
  • Failed / Canceled:读取失败(如路径不存在)会写入TerminalError并以Failed终结;收到取消事件则进入Canceled。任何来源不符、关联 ID 不符、输出偏移异常的事件都会导致操作失败,保证异步读写的严格一致性。

这种"操作 + primitive 事件"的双层设计,使得技能文件的读取可以跨多轮对话完成:模型这一轮发起SkillUse,harness 在后台读取文件,读取完成后将文件全文作为工具结果回传给模型,模型在下一轮拿到内容后即可遵循其中指令继续工作。

加载中的占位结果

由于读取是异步的,若模型在读取完成前就结束回合,TranslateResult会返回占位文本"Skill is loading."(skill_use.go);读取完成后,文件内容会被转换为 UTF-8 合法字符串(非法字节以\uFFFD替换)作为工具结果返回(skill_use.go)。这与 harness 的通用运行中工具占位Tool call is still running...(builder.go)逻辑一脉相承。

SKILL.md 相对路径解析规则详解

skill-preamble 中第三条指令是整个技能体系与文件系统交互的关键约定:

When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool calls.

拆解这条规则:

  1. 基准目录:技能目录 =SKILL.md所在目录的父目录,即dirname技能文件路径。例如技能文件位于/skills/reviewer's/SKILL.md,则技能目录是/skills/reviewer's/。
  2. 解析动作:技能文件内凡是引用了相对路径(比如指向同目录下的templates/task.md、scripts/run.sh),都以此技能目录为基准解析成绝对路径,而不是相对模型当前工作目录解析。
  3. 使用方式:解析出的绝对路径必须直接用于后续工具调用(如Bash执行脚本、ViewImage查看截图等)。

之所以做此约定,是因为技能文件是"可移植的指令包":技能作者在SKILL.md里写的路径天然相对于技能自身位置,而模型的工作目录可能因任务而异。统一按技能目录解析,能保证同一份技能在任何工作区下行为一致,避免"路径失效"类幻觉。

实战:一次完整的技能加载流程

综合上述实现,一次典型的技能加载可以归纳为五步(对应 builder.go、skill_use.go、skill_use.go 的调用链):

  1. 注入清单:请求构建时,NewBuilder把已注册技能序列化为<available_skills>XML,拼进系统提示词(含 skill-preamble 三条规则);
  2. 模型决策:模型发现当前任务与某技能的description匹配,发起SkillUse调用,参数为{"name": "<技能名>"};
  3. 翻译与提交:skillUseTranslator校验名称、查注册表拿到Path,构造 skill-use 操作并异步提交,本轮立即返回;
  4. 异步读取:操作状态机派发IOReadprimitive 读取SKILL.md全文,事件回流完成后把内容作为工具结果返回;
  5. 遵循执行:模型拿到SKILL.md内容,按其中指令工作;文件内相对路径一律按技能目录解析为绝对路径后再调用Bash等工具。

测试佐证方面,skill_use_test.go 演示了构造SkillUse调用并断言其解析为对应技能文件路径的过程;builder_test.go 则固化了下发到模型的技能清单 XML 快照。两者共同保证了"注册 → 注入 → 调用 → 读取"链路的行为稳定。

小结

Unreal Agent 的技能体系可以概括为一条简洁而完备的协议:skill-preamble 定义规则(用SkillUse按需加载、相对路径以技能目录为基准解析),<available_skills>XML 提供选择依据,SkillUse工具 + 异步 IO 操作负责把SKILL.md内容安全送达模型。这一设计让技能成为"可注册、可发现、可懒加载"的指令扩展单元,与 harness 的 async-first、操作可持久化架构深度契合。若要在你的 Unreal Agent 部署中接入自定义技能,只需遵循三步:把指令写进SKILL.md、以Skill三元组注册到 Registry、确保文件内部相对路径相对技能目录书写即可。

【免费下载链接】unreal-agent

Async-first agent harness

项目地址:https://gitcode.com/gh_mirrors/un/unreal-agent
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表