最近在整理 Agent 技能目录和站点文档导入流程时,我一直被一个问题绕住了:同一个项目里,既要让大模型快速找到整站文档的入口,又希望 Agent 能按固定流程执行具体任务,那到底该维护一个 llms.txt,还是写若干个 Skill.md?网上资料往往各讲各的,一个偏站点索引,一个偏 Agent 能力,读完之后还是搞不清两者是什么关系。
这篇文章就把 llms.txt 和 Skill.md 拆开讲清楚。文章会从文件格式、适用场景、编写步骤、自动生成脚本、常见坑点几个方面展开,最后给出一套“什么时候用哪个、什么时候两个都要”的判断标准。如果你正在做 RAG 知识库接入、给 Agent 配置技能包,或者想优化自己站点的 LLM 检索效果,这篇文章会比较适合你。
读完你能明白:llms.txt 解决的是“模型怎么找到内容入口”,Skill.md 解决的是“模型拿到任务后按什么步骤执行”。两者可以独立使用,也可以在同一条链路里配合使用。
1. 背景:模型不该靠“猜”来读取你的内容
1.1 从 robots.txt 到 llms.txt
在传统网站生态里,爬虫进入站点之前会先看一个叫robots.txt的文件。它告诉搜索引擎爬虫:哪些路径可以抓,哪些路径不能抓,sitemap 在哪里。这个机制的核心价值是“降低爬虫的探索成本”。
到了大模型时代,问题变得更加微妙。搜索引擎爬虫抓取网页是为了建立关键词索引,而大模型读取网页是为了理解语义、抽取信息、回答问题。网页里大量存在的导航栏、广告位、JS 动态渲染内容,对搜索引擎也许还能容忍,但对大模型来说可能是噪音,会直接干扰信息抽取效果。
llms.txt这个文件就是在这种背景下出现的。它是一份放在站点根目录下的纯文本索引,目标是给大模型提供一个“友好版站点地图”。当一个 AI 应用准备抓取某网站的文档时,可以先去读llms.txt,从而快速确定该抓哪些页面,而不是从首页开始层层遍历,浪费 Token 和时间。
1.2 从 Prompt 模板到 Skill.md
另一方面,随着 Agent 应用变得复杂,开发者发现一个问题:单纯靠系统提示词已经很难承载复杂技能。比如“生成周报”这件事,可能涉及输入格式校验、数据分类、模板渲染、结果输出等多个步骤。如果把所有这些步骤都塞进一个超长系统提示里,不仅维护困难,而且每次对话都要消耗大量上下文。
于是,很多 Agent 平台开始采用“技能包”的组织方式。一个技能对应一个目录,目录里放一个 Markdown 主文件,描述这个技能的用途、触发条件、执行步骤和输出格式。这个主文件常见命名就是skill.md或SKILL.md,我们这里统一用题目里的Skill.md来指代这类文件。
Skill.md的价值在于:它把“某个具体能力”从全局 Prompt 中剥离出来,让 Agent 按需加载。用户用到对应能力时,相关模型层才读取这个文件,既能节省上下文,也能让技能独立维护、独立复用。
1.3 为什么会同时出现两个文件
llms.txt和Skill.md看似都围绕大模型读取文件,但服务对象完全不同。一个是给“检索器”看的文档索引,一个是给“执行器”看的行为规范。很多项目中,检索器负责找到外部资料,执行器负责根据资料完成任务。如果执行器需要某个站点的资料,它可能先借助llms.txt获取入口,再借助Skill.md决定怎么处理资料。因此,两个文件完全可能出现在同一条链路中。
2. Skill.md 与 Llms.txt 分别解决了什么问题
2.1 Llms.txt:给检索者看的站点地图
llms.txt解决的核心问题是“目录发现”。传统搜索引擎有成熟的爬虫系统,可以自动发现站内页面;但大模型应用往往没有耐心,也不适合对整站做全量抓取。站点维护者如果手动提供一个精简索引,模型就能更高效地完成后续操作。
典型场景包括:
- 站点公开了大量技术文档,希望被 AI 应用准确引用。
- 企业内部知识库需要提供给内部 Agent 使用。
- 做 RAG 应用时,希望通过一个文件快速确认高质量文档 URL 列表。
llms.txt的核心思想是“少而精”。它不需要列出所有页面,只需要列出高质量、值得 AI 阅读的入口页面。后续无论是抓取正文,还是由模型判断相关性和进入哪个子页面,效率都会更高。
2.2 Skill.md:给执行者看的操作手册
Skill.md解决的核心问题是“行为约束”。当 Agent 接收到一个任务时,它需要知道该调用什么工具、按什么步骤操作、输出什么格式。这些信息不应该是隐式的,而应该写成一份清晰的操作手册,由 Agent 在需要时加载。
典型场景包括:
- Agent 需要按照固定流程生成日报、周报。
- Agent 需要从数据库查询信息并按指定模板输出。
- Agent 需要接入外部 API,通过技能文件描述 API 的调用参数和错误处理方式。
- 多 Agent 系统中,不同角色通过各自技能文件保持行为一致。
简单说,llms.txt回答“用户可以去哪里找答案”,Skill.md回答“Agent 该如何完成任务”。
2.3 核心区别:站点级入口 vs 能力级说明
把两者放在一起对比,最核心的区别可以概括为一句话:llms.txt是站点级的“内容目录”,Skill.md是能力级的“操作说明书”。
在文件粒度上,一个站点通常只有一个llms.txt,但可能会有多个Skill.md,每个技能一个文件。在服务对象上,llms.txt服务于检索型应用,比如 RAG 管道;Skill.md服务于 Agent 型应用,比如自动执行任务的智能体。在使用方式上,llms.txt的内容往往直接注入到检索流程中用于选择 URL,Skill.md的内容则会按需注入到 Prompt 中指导模型行为。
3. 文件格式与语法拆解
3.1 llms.txt 文件格式
llms.txt的格式非常轻量,基于 Markdown 的子集。文件第一行使用一级标题#写站点名称,后续每一行基于文件列表安排。一个最简单的示例:
# Example Developer Documentation ## https://example.com/ ## https://example.com/getting-started/ ## https://example.com/api/reference/ ## https://example.com/guides/从结构上看,一级标题是站点名字,二级标题行直接写页面 URL。这样的写法很克制,方便解析器逐行处理。解析器通常只需要识别#开头的站点名,以及##开头的 URL 行,就能得到一个干净的入口列表。
有些站点会在llms.txt中加入分组标题和链接列表,比如:
# Example Developer Documentation ## Quick Start - [Installation Guide](https://example.com/install) - [Configuration Guide](https://example.com/config) ## API Reference - [REST API](https://example.com/api)这种写法更易读,也符合 Markdown 习惯。需要注意,社区规范对“哪些 Markdown 语法可用”是有边界的,设计初心是尽量保持简洁、减少解析歧义。实际落地时,建议以官方规范页面为准,并在生成后做一次自动解析验证。
3.2 Skill.md 文件格式
Skill.md的结构通常分成两块:头部元数据和正文说明。
头部元数据一般使用 YAML frontmatter,也就是被三条短横线---包裹的区域。这块区域用于存放机器可读的字段,比如技能名称、用途描述、版本号、作者、触发关键词等。正文部分是自然语言说明,供模型理解具体执行流程。
一个常见的Skill.md结构如下:
--- name: weekly-report description: 根据用户提供的本周工作内容生成结构化周报 Markdown 文件。 version: 0.1.0 license: MIT metadata: author: dev-team trigger_words: - 周报 - 周总结 - weekly report --- # 周报生成技能 ## 目标 生成一份结构清晰、信息准确的周报文件。 ## 使用条件 - 用户提供了本周任务列表或关键进展; - 如果用户只描述了模糊目标,应先追问细节,再开始生成。 ## 执行步骤 1. 汇总用户输入的原始内容; 2. 将内容按“目标 / 进展 / 风险 / 下周计划”归类; 3. 生成 Markdown 周报文件; 4. 输出后请用户确认是否补充遗漏事项。 ## 输出模板 ```markdown # 周报 ## 本周目标 ## 本周进展 ## 风险与问题 ## 下周计划 ```这部分内容看似简单,但对 Agent 的行为影响很大。frontmatter 中的name是技能唯一标识,description用于技能匹配,正文的“执行步骤”则决定了模型生成结果的质量。
3.3 很多人问:Skill.md 里面 # 后面的内容是不是不执行
这个疑问其实暴露了 Markdown 和代码执行的混淆。要回答清楚,需要区分两个位置:frontmatter 区域内和正文区域。
在 frontmatter 区域中,#是 YAML 注释的开始符号。例如:
--- name: weekly-report # 这是一行 YAML 注释,解析器会忽略它 description: 生成周报 ---这里的注释在解析阶段会被忽略,不会成为字段值。也就是说,“#后面不生效”在 YAML 注释场景下基本成立。
但在正文区域中,#是 Markdown 标题语法。比如# 周报生成技能是一个一级标题,模型读取文件时会把这段话当作文档结构的一部分,用于理解后续内容。它不会被当作命令“执行”,也不会被整体跳过。模型读取的是完整文本,并把每个标题当成语义信息。
所以更准确的说法是:Skill.md中的 Markdown 内容不是可执行代码,不存在“执行”或“不执行”的区别;但 frontmatter 中的 YAML 注释会被解析器忽略,正文中的 Markdown 标题会参与模型语义理解。如果你有内部备注不想让模型看到,不应写在正文里,而应放在 frontmatter 注释中,或者干脆放在技能目录外的独立文件里。
4. 实战:为你的站点生成 llms.txt
4.1 准备工作与环境
生成llms.txt不需要太复杂的环境。你需要:
- 一个文本编辑器,用于查看和修改最终文件。
- 命令行工具,推荐使用
curl验证文件是否可访问。 - 如果希望自动生成,需要安装 Python 3.8 以上版本。
本文示例以常见环境为主,版本需要根据你的项目实际情况调整,重点演示配置思路。
4.2 手动编写一份 llms.txt
假设你的站点是https://docs.example.com/,主要文档分成“快速开始”“API 参考”“最佳实践”三个模块。那么一份手写的llms.txt可以是:
# Example Docs ## https://docs.example.com/ ## https://docs.example.com/getting-started ## https://docs.example.com/api-reference ## https://docs.example.com/best-practices生成后,把文件保存为llms.txt,并放到站点根目录下。上线前用curl验证地址是否能正常返回:
curl https://docs.example.com/llms.txt如果返回的是文件内容而不是 404,说明部署位置正确。要注意的是,文件名必须是llms.txt,不要写成LLMS.txt或llms.txt/,服务器路径大小写敏感时容易踩坑。
4.3 用 Python 脚本自动抓取生成
手动维护虽然简单,但站点页面一多就容易遗漏。这里提供一个轻量级 Python 脚本思路:从站点根页面抓取所有同域名链接,然后输出成llms.txt的骨架。
# 文件路径:generate_llmstxt.py import argparse import re from urllib.parse import urljoin, urlparse from urllib.request import urlopen def get_links(base_url, html): links = [] for href in re.findall(r'href=["\']([^"\']+)["\']', html, re.I): absolute = urljoin(base_url, href) links.append(absolute) return links def same_domain(url, base): return urlparse(url).netloc == urlparse(base).netloc def main(): parser = argparse.ArgumentParser(description="Generate llms.txt skeleton") parser.add_argument("--root", required=True, help="Site root URL") args = parser.parse_args() root = args.root.rstrip("/") with urlopen(root) as resp: html = resp.read().decode("utf-8", errors="ignore") seen = [] for link in get_links(root, html): if same_domain(link, root) and link not in seen: seen.append(link) print(f"# {root}") print() for link in seen: print(f"## {link}") if __name__ == "__main__": main()运行方式:
python generate_llmstxt.py --root https://docs.example.com这个脚本只是提取同域名链接,输出结果可能包含登录页、分页、动态路由等不需要的内容。它更适合作为初稿,生成后一定要人工清理,只保留高质量的文档入口。
4.4 验证文件是否可被有效读取
生成并部署后,可以从两个层面验证。
第一层是可访问性。确认 URL 能直接返回内容,不依赖 JS 渲染,没有强制跳转。大模型应用抓取时不会执行复杂脚本,所以llms.txt必须是静态文本。
第二层是解析符合预期。可以写一段简单脚本读取llms.txt,提取所有##开头的 URL:
urls = [] with open("llms.txt", "r", encoding="utf-8") as f: for line in f: line = line.strip() if line.startswith("## "): urls.append(line[3:].strip()) print(urls)如果输出的 URL 列表符合预期,说明解析器可以正常拿到入口。建议把这一步纳入站点发布流程,避免文件更新后结构被误改。
5. 实战:编写一个可用的 Skill.md
5.1 技能目录规划
编写Skill.md之前,先想清楚技能边界。一个技能最好只做一件事。比如“生成周报”是一个技能,“获取天气并生成早安推送”是另一个技能,不要混在一起。
常见目录结构如下:
skills/ weekly-report/ skill.md templates/ weekly_report_template.md scripts/ format_report.py这个结构把技能说明、模板、脚本分层存放。skill.md负责告诉模型“该怎么做”,templates放输出模板,scripts放真正需要执行的代码。
5.2 编写 frontmatter
frontmatter 中最关键的字段是description。很多 Agent 平台会通过这个字段判断“用户请求是否匹配当前技能”。它写得太泛,会导致误触发;写得太窄,又会导致技能无法被唤醒。
以一个“客户周报生成”技能为例:
--- name: customer-weekly-report description: 当用户需要基于客户沟通记录生成周报时使用。适用于销售、客户成功、项目经理等角色。 version: 1.0.0 license: MIT metadata: author: example-team trigger_words: - 客户周报 - 客户进度 - 客户沟通总结 ---这里trigger_words用于提示模型哪些请求可能和本技能相关。它不是硬编码的触发条件,而是辅助路由的语义线索。
5.3 编写正文工作流
正文是技能的核心,承担着“指导模型执行”的职责。正文应该尽量结构化,让模型能按步骤执行,而不是给一段含糊的长文本。
# 客户周报生成技能 ## 适用场景 用户需要输出某个客户的本周沟通总结或项目进度周报。 ## 输入信息 - 客户名称; - 本周沟通记录或关键事件; - 如果缺少上述信息,先向用户确认,不要自行编造。 ## 处理步骤 1. 提取客户名称和时间范围; 2. 从沟通记录中归纳关键进展; 3. 标注风险项和待办事项; 4. 按模板输出周报。 ## 输出格式 ```markdown # 客户周报:{客户名称} - 时间范围:{起始日期} 至 {结束日期} - 本周进展:... - 风险项:... - 待办事项:... ```这段正文的价值在于:它把模型的行为约束在一个明确范围内。比如“不要自行编造”就是非常关键的一条,能有效避免模型完成任务时虚构客户信息。
5.4 部署到 Agent 环境
不同 Agent 平台加载技能的方式不同。有的平台要求把技能目录放到特定目录下,有的平台要求通过管理界面导入。部署时务必关注三点:
第一,文件名大小写。部分平台约定为skill.md,部分平台约定为SKILL.md,还有平台允许两者。落地前先查一次平台文档,别因为文件名字母大小写导致技能加载失败。
第二,目录相对位置。技能主文件通常放在该技能目录的根目录,但具体路径受平台约束。例如有些平台要求技能目录放在skills/{skill-name}/下,有些则放在~/.claude/skills/下。需要按官方说明安排。
第三,依赖文件路径。如果skill.md中引用了模板或脚本,尽量使用相对路径,并在部署后测试一次完整调用。
5.5 验证与迭代
部署完成后,用一组测试请求验证技能效果。可以先准备 3 类输入:明确触发词、模糊意图、完全不相关内容。观察模型是否正确唤醒技能,以及输出质量是否符合预期。
如果技能没有被唤醒,优先检查description是否覆盖了用户常用说法。如果技能被错误唤醒,说明description和trigger_words写得太宽,需要收紧。如果技能唤醒后输出格式不对,重点检查正文中的“输出格式”部分是否足够具体。
6. 二选一还是都要:判断标准与最佳实践
6.1 什么时候只需要 llms.txt
如果你的目的只是让大模型能准确找到站点内容,而不是让 Agent 执行复杂任务,那么只需要llms.txt。
典型情况包括:公开文档站点、开源项目主页、企业对外 API 文档。这类场景里,模型只需要“读什么”,不需要“做什么”。引入Skill.md反而增加维护成本。
6.2 什么时候需要 Skill.md
如果你正在构建 Agent 应用,且 Agent 需要执行固定流程的任务,那么Skill.md是更合适的选择。
例如内部运营机器人需要根据数据自动生成日报、客服助手需要按固定话术回复常见问题、研发助手需要按照既定流程创建 Issue。这些场景的关键不是“找到内容”,而是“按规范完成动作”。
6.3 什么时候两者同时维护
两者同时维护的场景并不罕见。一个知识型 Agent 产品,既需要llms.txt让模型快速抓取外部资料,又需要Skill.md让模型按照统一流程整理资料、输出报告。
还有一种常见的协同方式是:在Skill.md的执行步骤中引用某个站点的llms.txt,要求模型先读取该文件确定资料入口,再抓取细节。这样llms.txt提供信息供给,Skill.md提供行为约束,各自发挥优势。
6.4 工程建议
无论维护哪个文件,都建议遵循以下几条原则:
- 保持精简。
llms.txt不是全站链接备份,Skill.md不是长篇大论,文件内容越聚焦,模型使用效果越好。 - 纳入版本管理。
llms.txt和Skill.md都应该存放在 Git 仓库中,方便追踪变更历史。 - 加入自动化校验。可以写脚本检查
llms.txt中的 URL 是否可访问,也可以写脚本校验Skill.md的 frontmatter 字段是否完整。 - 敏感信息隔离。不要在
Skill.md中写入 API Key、数据库密码、内部凭据,应通过环境变量或密钥管理服务注入。 - 最小权限意识。给 Agent 配置技能文件时,只授予完成任务所需的最小权限。例如能只读就不要写,能限定目录就不要开放全盘访问。
7. 常见问题与排查思路
下面列举几个实际使用中容易遇到的问题。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
模型总是找不到llms.txt | 文件没有放在站点根目录,或文件名大小写不对 | 确认 URL 为https://domain/llms.txt,并检查服务器静态文件配置 |
llms.txt中 URL 太多,模型理解困难 | 把全站链接都塞进去了 | 只保留高质量入口,合并同类页面,控制文件在少量链接量级 |
Skill.md部署后技能没有被唤醒 | description写得太窄或太泛,和用户表达不匹配 | 重写description,覆盖常见相似表达,并加入trigger_words |
| 技能被错误唤醒 | 技能描述中关键词覆盖范围过大 | 增加使用条件限制,在正文中要求模型先确认输入是否匹配 |
frontmatter 中的#注释被当成了字段 | 对 YAML 注释语法不熟练 | 明确注释只写在 frontmatter 区域,正文中的#请当作 Markdown 标题处理 |
| 生成周报时模型编造了数据 | Skill.md正文没有明确“禁止编造”约束 | 在执行步骤中加入“缺少信息先询问、不要自行编造”的描述 |
修改Skill.md后行为没有变化 | Agent 平台缓存了旧技能文件 | 重启相关服务或等待缓存过期,必要时查看平台加载日志 |
如果你遇到“技能目录文件加载失败”类问题,可以按以下顺序排查:
- 检查文件名是否完全符合平台约定。
- 检查文件是否保存为 UTF-8 编码。
- 检查 frontmatter 是否使用三条短横线闭合,字段是否缩进规范。
- 检查是否有特殊符号导致 YAML 解析失败。
- 查看 Agent 平台日志,确认技能加载错误信息。
8. 总结
llms.txt和Skill.md并不冲突,它们服务的是大模型应用的不同环节。llms.txt更像“图书馆索引”,告诉模型该去看哪几本书;Skill.md更像“实验手册”,告诉模型该按哪个步骤完成操作。一个偏内容发现,一个偏行为控制。
实际项目中,先问自己一个问题:这个文件是给谁看的,解决什么问题?如果是为了让模型更快找到站点内容,优先维护llms.txt;如果是为了让 Agent 稳定完成特定工作流,优先维护Skill.md;如果两者都存在,就把它们放到各自合适的位置,让llms.txt提供素材、Skill.md提供流程。
两个文件都不复杂,真正考验人的是边界意识。下次新建一个技能目录或站点索引文件时,先想清楚它是“入口”还是“操作手册”,写出来的内容自然会清晰很多。如果你在项目中也遇到过相关踩坑,欢迎在评论区分享当时的处理思路。