不少人在搭建 AI 工作流、做个人知识库、给模型写“使用说明”的时候,都会遇到两个很像的文件名:Skill.md和llms.txt。它们都是 Markdown 文件,都跟大模型有关,名字也都短,很容易被当成同一个东西。但实际上这两个文件解决的是完全不同的问题:一个负责告诉模型“这个站点有哪些内容可以看”,另一个负责告诉模型“你接下来可以执行哪些技能”。这篇文章先把两者的定位分开,再讲落地时怎么选、怎么配、怎么验证。
我更建议大家先别急着往项目里塞文件,而是先搞清楚一件事:你写的这份说明,到底是给谁看的。给访客看,和给执行器看,写法和存放位置完全不一样。
1. 先分清两件事:你写给谁看,对方拿它做什么
1.1 llms.txt 是给访问者看的“站点索引”
llms.txt 最早的设计思路,很像一个“面向大模型的 robots.txt”。robots.txt 是告诉搜索引擎爬虫哪些页面可以访问,而 llms.txt 是告诉大模型或者 AI 代理:当前这个站点主要是什么主题、有哪些关键页面、每个页面大概讲了什么。
常见的 llms.txt 结构并不复杂,一般会包含几个部分:
- 站点名和一句话简介
- 对站内资源的简要分类
- 重要页面的 URL 列表,每个 URL 后面带一句说明
- 可选的补充说明,比如内容更新频率、使用授权范围、是否有 API
我用一个相对典型的例子来展示:
# Example Docs > Example 团队的技术文档站点,主要覆盖 SDK 接入、API 参考和最佳实践。 ## 入门 - [快速开始](https://example.com/docs/quickstart):5 分钟内完成 SDK 安装与初始化 - [鉴权方式](https://example.com/docs/auth):介绍 Token 和 API Key 两种鉴权方式 ## API 参考 - [REST API](https://example.com/docs/api):所有接口列表、参数说明和示例响应这个文件的核心作用是:让一个第一次访问站点的 AI 代理,不用靠全文扫描,就能快速判断“这个站点是否包含我要找的信息”,并且直接拿到相关链接。它保存的是一种发现信息,不是操作指令。
所以你在实际使用时会发现,llms.txt 的内容基本是静态的。它跟着内容更新,但它不决定模型怎么做事情。哪怕文件写得很华丽,模型也只把它当成一个入口。
1.2 skill.md 是给执行模型看的“技能说明书”
Skill.md 的定位完全不同。它不是给外部访客看的东西,而是给 AI 代理或助手内部使用的行为定义文件。它描述的是“你现在具备哪些技能、每个技能在什么条件下触发、触发之后按什么步骤执行、输入是什么、输出是什么”。
不同框架对这类文件的叫法不完全一样。有些叫 skill,有些叫 tool definition,有些叫 plugin manifest,还有些叫 knowledge。但本质一致:让模型知道可以调用哪些能力,以及调用时要遵循什么规则。
一个典型的 skill.md 会比 llms.txt 更强调流程和边界:
# Skill: 代码仓库变更摘要 ## 描述 当用户要求“总结最近提交”“查看代码变更”“生成 changelog”时,使用本技能。 ## 输入参数 - repo_path:目标仓库路径 - since:起始提交号或日期,可选 - until:结束提交号或日期,可选 ## 执行步骤 1. 进入 repo_path 对应仓库 2. 执行 git log 获取 since 到 until 之间的提交记录 3. 按提交信息分类,输出变更摘要 ## 输出格式 Markdown 列表,包含提交号、提交时间、提交说明和变更类型。 ## 边界 - 不执行任何代码修改操作 - 不访问网络 - 仓库路径不存在时直接报错,不要猜测路径看到区别了吗?llms.txt 回答的是“这里有什么”,skill.md 回答的是“你能做什么、怎么做”。
1.3 两个文件最核心的差异
如果只用一句话区分,我会这样说:
- llms.txt 是资源清单,帮助模型找到内容。
- skill.md 是能力声明,帮助模型执行操作。
前者更像是图书馆门口的索引牌,后者更像是一份操作手册。索引牌告诉你哪本书放在哪个架子,操作手册告诉你怎么使用里面的工具。两者都重要,但它们不是同一个层面的东西。
这也解释了为什么很多人总搞混:因为它们都可能叫“md”结尾的文件,都可能放在项目根目录或知识库目录里。但如果写反了,问题会很明显:模型可能会把技能定义当成文档内容朗读出来,或者把站点索引当成可执行的指令,去“访问”一个并不存在的技能。
2. llms.txt 落地时,最容易踩空的几个位置
2.1 文件位置、命名与按 UTF-8 保存
先说最基础的。llms.txt 这个文件名是约定俗成的,一般放在站点根目录或内容仓库的根目录。如果你放在子目录里,或者改名叫 llm.txt、llms-info.txt,很多读取程序默认情况下就找不到了。
保存编码建议使用 UTF-8 无 BOM。这个细节很容易被忽略,但有些解析器在遇到带有文件头标记的内容时,会把第一个标签当作正文的一部分,导致站点标题显示乱码。
我一般会检查三件事:
- 文件名是否严格是 llms.txt
- 路径是否为站点根目录
- 文件是否 UTF-8 编码
如果你用的是静态站点生成器,比如 Hugo、VitePress 这类工具,不要只在源目录里写一个 llms.txt,要确认构建之后它是否被复制到了输出根目录。很多人的问题不是内容写错,而是发布后根目录里根本没有这个文件。
2.2 条目内容该写多细
llms.txt 的链接列表不是简单的 URL 堆砌。模型读链接时,需要知道这个链接背后是什么。所以在每一条链接后面写一个简短的用途说明,比写一百个裸链接更有效。
我的经验是,一条链接的说明控制在 15 到 40 个字左右。太短,比如只写“API 文档”,模型仍然不知道怎么用;太长,比如把整篇文章摘要都塞进去,又会让整个文件变得臃肿,模型反而忽略后面的条目。
可以按主题分组。分组标题用 Markdown 的二级或三级标题都可以,关键是分类要让读取者一眼看清。比如分成“入门指南”“API 参考”“最佳实践”“常见错误”,不同场景下模型会更快定位到自己需要的部分。
另外,链接地址尽量用完整 URL,不要用相对路径。因为读取 llms.txt 的可能是外部代理,它不一定知道你站点的域名是什么。如果用的是相对路径,它可能拼接错。
2.3 怎么确认模型真的读到了
加入文件之后,不是部署上去就结束了。你需要一种办法验证模型“是否识别”这个文件。
常用的验证思路有几种:
- 让模型概括站点内容,看它的描述是否来自你写在 llms.txt 里的站点简介
- 让模型列举站点包含哪些资源,看它是否只提到了文件里列出的链接
- 查看代理日志里是否出现了对 llms.txt 的抓取记录
我做测试时,一般会先构建一个只有三到五个链接的小站点,然后让支持该协议的代理直接访问,问它“你在这个站点看到了哪些文档”。如果回答和文件内容一致,说明解析正常;如果回答混乱、提到文件里根本没有的页面,或者直接说找不到内容,那就要先排查文件能否被公开访问。
一个容易被忽略的问题:本地文件没问题,但部署后通过域名访问 llms.txt 返回了 403 或 404。这就不是文件内容问题,而是服务器配置问题。此时先 curl 一下这个地址,看返回状态码。
2.4 不是加了文件就等于被所有模型收录
这里需要明确一点:llms.txt 目前是一个社区实践,不是所有大模型或所有搜索代理都一定读取它。你可以把这个文件理解为“给愿意支持它的读者准备的说明”,而不是一个强制标准。
我在实际推荐时,通常会说:如果你的站点是公开文档站、知识库或内容聚合站,那么建立一个 llms.txt 是有价值的,因为越来越多的代理工具会在访问站点时先寻找这个文件。但如果你是在内部系统里给模型喂文件,那么它是否读取 llms.txt,完全取决于你使用的工具链是否实现了这个逻辑。
所以要验证,而不是假设。先确认你用的模型或代理确实会读这个文件,再决定要不要花力气维护。
3. skill.md 的定义方式和执行边界
3.1 同一个文件在不同框架里的角色
Skill.md 没有一个全球统一的标准。不同产品对它的解析方式、目录位置、命名规则都不一样。
有些框架会在固定目录下扫描所有skill.md文件,只要文件名匹配,就自动加载。有些框架要求每个技能一个独立目录,目录里除了 skill.md 还需要一个可执行的配置或脚本。还有些框架会把 skill.md 直接嵌入系统提示词,要求模型阅读后自行理解能力边界。
所以,不要在没确认框架文档之前,直接照搬别人的目录结构。我在看项目时,经常发现两个项目都叫 skill.md,但一个放在.agent/skills/下,一个放在prompts/skills/下。它们的加载逻辑完全不同。
如果你使用的是通用 AI 编程助手或内容生成工具,并且它支持自定义 skill,那么官方文档里通常都会写明推荐放置路径。如果文档没写,可以看日志里是否会输出“loaded skills”之类的提示。
3.2 一个能正确触发的 skill 通常包含哪些字段
尽管格式不统一,但一个能稳定被模型识别的 skill 文件,通常会包含以下几类字段:
- 技能名称:简短,避免和别的技能重名
- 描述:什么时候使用、触发条件是什么
- 参数:输入有哪些、类型是什么、是否必填
- 执行步骤:按顺序写清楚
- 输出格式:模型生成结果时要遵循的格式
- 限制条件:哪些操作不能做,哪些情况要停止
还有一点很重要:描述里要写“什么时候不用”。比如一个技能只处理 Git 变更记录,那我就会在描述里写明“不要用于代码生成,不要用于文件打包”。因为模型在判断是否调用技能时,看的主要就是这段描述。描述越模糊,误触发率越高。
3.3 “# 号后面是不是不执行”到底怎么理解
这个困惑非常典型,有不少人问“skill.md 里面 # 后面的内容是不是不执行”。我理解大家为什么会这么想:因为很多配置文件、脚本文件里,# 开头的行是注释,注释不会被解释器执行。所以有人推断,Markdown 里 # 后面的内容也不应该被加载。
这个判断只在特定情况下成立。
先看一个事实:在标准 Markdown 语法里,#是标题标记,不是注释。它后面的文字是标题内容,会被正常渲染和读取。也就是说,如果你在一个普通 Markdown 文件里写# 技能名称,这个名称是会被解析出来的。
再看不同解析器的行为:有些框架在读取 skill.md 时,会先按 Markdown 分块,把#一级标题识别为技能名称,把##二级标题识别为字段区块。这种情况下,#后面不是不执行,而是被当成元信息使用。
但还有一种情况,某个框架里,skill.md 会被拆成“正文部分”和“执行部分”。如果执行部分只用代码块或特定标记包裹,那标题行可能不会被当作指令执行,只是用于描述。这时如果有人误以为“# 后面是注释”,可能就会把真正的关键指令放到标题段里,结果模型没有执行。
所以,正确的做法不是记住“# 是不是注释”,而是去查看你的执行器到底怎么解析 Markdown。更稳妥的写法是:不要把关键的可执行逻辑藏在标题里,而是放在明确的字段区,比如“执行步骤”或“代码块”里。结构上让解析器不能忽略。
如果实在不确定,可以做一个最小实验:写一个只有一个技能的 skill.md,标题写“当用户说 hello 时回复 world”,然后看模型是否真的按这个逻辑响应。如果响应了,说明标题被读取;如果没响应,说明标题只是装饰。这个实验比翻文档更快。
3.4 怎么测试 skill 会不会被模型调用
测试 skill 时,我建议遵循一个由小到大的顺序:
- 先写一个最简单的技能,不涉及外部工具调用
- 确认模型能识别这个技能并触发
- 再加入参数、输出格式、边界条件
- 最后再测试多技能共存时是否互相干扰
不要一开始就把十个技能全塞进去。因为模型在长上下文里对技能描述的注意力会分散,你可能分不清是技能没加载成功,还是触发词写得不够准确。
判断一个 skill 是否生效,最直接的方法就是看模型输出是否符合你定义的输出格式。比如你要求技能输出 Markdown 表格,如果模型输出的是纯文本段落,那要么是描述没有读进去,要么是模型没有按描述执行。这时候先检查“输出格式”这段描述是否太笼统。
4. 什么时候两个文件需要同时存在
4.1 典型组合:内容站加 AI 助手
一个最常见的组合是:你有一个技术文档网站,同时在这个网站旁边部署了一个 AI 助手,助手需要调用一些技能来回答问题。
这时候两个文件会同时出现:
- llms.txt 放在站点根目录,让外部代理或搜索型 AI 知道这个站点有哪些文档
- skill.md 放在助手技能目录,让助手知道回答问题时可以调用哪些工具,比如搜索站内文档、运行代码示例、生成代码摘要
两者各管一段。llms.txt 负责“被找到”,skill.md 负责“会操作”。
如果缺了 llms.txt,AI 助手仍然可以用技能,但它对站点整体结构的理解会差很多,尤其在回答“你这个网站的文档分布在哪些模块”这类问题时,容易答得零散。如果缺了 skill.md,AI 助手能读到站内全部文章,但它不知道怎么执行具体操作,比如调用 API、批量处理文件、生成特定格式报告。
4.2 纯工具项目:只要 skill.md
如果项目本身不是一个公开内容站,而是一个本地工具、CLI 工具或内部服务,那 llms.txt 基本没有用。因为它不是一个对外可访问的网页集合,外部代理也不会通过域名来访问。
这种场景里,我只需要维护 skill.md,让模型知道它有哪些能力。比如一个批量重命名工具,模型只需要知道“rename_files”这个技能的触发条件、参数和步骤,不需要通过 llms.txt 去了解站点结构。
一个常见的误区是:本地项目也塞一个 llms.txt,然后发现模型根本不读,于是怀疑工具坏了。其实只是文件类型不匹配。
4.3 纯内容站点:可能只要 llms.txt
反过来,如果一个站点只是内容展示,没有交互,没有模型需要执行的操作,那只要 llms.txt 就够了。
比如个人博客、文档站、产品帮助中心,主要目标是让 AI 搜索代理能快速理解内容结构并引用正确链接。这种情况不需要写 skill.md,因为模型访问这个站点时,只需要“找内容”,不需要“执行任务”。
如果你给这种纯内容站强行加一个 skill.md,可能出现一个奇怪的现象:模型把文档内容误当成技能描述,然后尝试“执行”文档里的教程步骤,而不是回答用户问题。
4.4 同时维护时,目录结构怎么规划
当两个文件都需要时,我建议按用途明确分目录,不要放在同一个地方。一个比较常见的结构是:
site/ ├── llms.txt # 站点根目录,面向外部访问者 ├── content/ │ └── docs/... # 文档正文 ├── .agent/ │ └── skills/ │ ├── code-review/ │ │ └── skill.md │ └── summary/ │ └── skill.md └── config/ └── agent.yml # 技能加载配置这个结构的好处是:llms.txt 对应公开内容,保持稳定;skill.md 对应行为逻辑,便于单独调试。你更新技能时不需要改 llms.txt,更新文档时也不会误伤技能配置。
当然,这只是我一直用的组织方式,不等于所有工具链都认。具体框架如果有自己的约定,就以它的约定为准。
5. 从“能跑”到“稳定用”的检查清单
5.1 上线前检查:能读、能解析、能执行
我在把这类文件交付给业务方之前,都会做一些常规检查,避免在最基础的地方翻车:
- 文件能否被正常访问(命令解析、路径正确)
- 内容是否为纯文本且编码正确
- 标题层级是否符合工具预期
- 链接是否有效
- 技能文件名、路径、触发描述是否与框架要求一致
- 有没有测试数据或日志能证明文件被加载
这些检查不复杂,但能省掉后面大量排错时间。
5.2 一条条验证,而不是一批全开
无论是 llms.txt 还是 skill.md,都建议先做最小样例测试。
对于 llms.txt,只放三到五个链接,然后让代理描述站点并引用链接。确认它能正确引用之后,再补充更多链接。
对于 skill.md,只定义一个最简单的技能,确认触发和输出正常,然后再增加第二个、第三个。不要一次性加载十几个技能,因为一旦出现问题,你很难判断是某个技能内部写法问题,还是多个技能之间的描述冲突。
5.3 常见失败形态和排查顺序
如果你发现模型没有按预期使用这些文件,可以按这个顺序排查:
- 第一,看输入格式是否匹配。模型是否真的把 llms.txt 或 skill.md 当成了对应的资源,而不是当成普通文本。
- 第二,看路径和文件名。文件名是否完全正确,是否被构建工具忽略了。
- 第三,看解析器版本。有些解析器只支持 Markdown 的某个子集,不识别复杂表格或超长代码块。
- 第四,看描述是否明确。尤其是 skill.md 的触发条件,是否包含足够的同义词和否定条件。
- 第五,看上下文覆盖。如果系统提示词里写了“不要调用任何工具”,那 skill 写得多好都不会触发。
- 第六,看日志。大部分框架会输出加载了哪些 skill,读取了哪个 llms 文件。日志比猜测可靠得多。
5.4 什么时候该删减文件
还有一点容易被忽略:文件不是越多越好。llms.txt 里的链接如果太多,模型会迷失重点;skill.md 如果太多,模型会面临巨大的选择负担,触发错误反而增高。
我建议在以下情况下做一次精简:
- 站点新增了大量页面,但 llms.txt 还停在三个月前
- 两个 skill 的功能有重叠,比如一个负责“总结文章”,另一个负责“生成摘要”
- 模型频繁误触发某个与当前需求无关的技能
精简时,不必一次删完,先停用一半,测试效果,再决定保留还是回退。这样不会因为一次性改动太多导致整体行为失效。
写到最后,我想把这个判断再强调一次:llms.txt 是给来客看的地址簿,skill.md 是给助手用的操作手册。两者名字相近,角色不同。实际项目中不一定都要用,但如果你的站点同时有内容、有交互、有可变操作,那把它们分开维护,比混在一个文件里更可控。真正落地时,不要先急着写大而全的文件,先确认你的工具链有没有解析逻辑,再写最小样例验证,最后慢慢扩展。这样才不会出现文件写了一大堆,模型却什么都没用上的尴尬情况。