llms.txt:给 Agent 的网站说明书
古董级程序员,从大厂到创业公司,现在还在一线做 AI 相关开发。微信公众号「字与码」会继续更新工程实践、新技术判断,以及这些年踩过的坑。文章若对你有用,欢迎顺手关注。
在浏览一个陌生技术网站时,人会先看导航、搜索框和“快速开始”。Agent 面临的页面却可能完全不同:动态渲染、折叠菜单、广告、登录弹窗,还有几百个内容相近的链接。它真正需要的通常不是整站 HTML,而是一张可信的“入口地图”。
llms.txt 就是在解决这个问题。
它看起来很像 robots.txt:都放在网站根目录,都是纯文本,也都面向机器。但两者的职责完全不同。robots.txt 回答“哪些内容允许爬”,llms.txt 回答“这个网站是什么,最值得读的资料在哪里”。
这篇文章不把它包装成新的 SEO 神器。我更关心三个工程问题:
- 一个 Agent 能不能稳定解析它?
- 文件变长以后,是否真的比网站导航更省上下文?
- 文档、版本和链接变化时,怎样避免它悄悄过期?
先把 llms.txt 放到正确的位置
它是提案,不是强制标准
llms.txt 由 Jeremy Howard 在 2024 年提出,提案页面对它的定位很克制:在推理阶段,为大模型提供简洁、专业、容易处理的网站背景和重要文件入口。
这里有两个容易被忽略的事实。
第一,它还不是 IETF RFC、W3C Recommendation,也不是浏览器必须支持的协议。网站发布了,不等于所有模型、搜索引擎和 Agent 都会自动读取。能否使用,取决于 Agent 的抓取器、搜索工具或运行时有没有实现这条发现规则。
第二,它主要服务的是推理时发现,不是声明训练授权。训练、爬取和索引策略仍然应该由服务条款、robots.txt、身份认证和访问控制决定。
所以更准确的理解是:
llms.txt是一份面向 Agent 的精选文档索引,而不是权限文件,也不是 API 契约。
它和其他机器文件各管什么
| 文件或协议 | 回答的问题 | 是否适合替代 llms.txt |
|---|---|---|
robots.txt | 爬虫是否可以访问某条路径 | 不适合,它不解释内容 |
sitemap.xml | 网站有哪些可索引页面 | 不适合,链接通常太多且没有取舍 |
| OpenAPI | 接口有哪些参数、响应和错误 | 不适合,但应由 llms.txt 链接过去 |
| MCP | Agent 如何发现并调用运行时工具 | 不适合,它是运行协议 |
AGENTS.md | 代码 Agent 在仓库里应遵循什么规则 | 不适合,作用域是代码仓库 |
llms-full.txt | 可一次加载的完整文档上下文 | 不能替代根索引,体积可能很大 |
一套成熟的机器文档不是只放一个文件,而是让这些入口互相配合。

Agent 实际需要的是一条可信路径
理想路径应该足够短:
/llms.txt
-> 快速开始或 Agent 接入说明
-> 某个产品的 llms.txt
-> 页面级 Markdown
-> OpenAPI / SDK Reference / 示例代码
根文件负责导航,不负责塞进所有答案。页面级 Markdown 负责内容,OpenAPI 负责接口契约,运行时仍然通过 API、MCP 或 CLI 完成调用。
提案规定了什么格式
最小结构并不复杂
根据格式说明,一个符合提案的文件按顺序包含:
- 一个 H1,写项目或网站名称。这是唯一必需部分。
- 一个可选的引用块,用一小段话概括项目。
- 若干不使用标题的补充说明。
- 若干 H2,每个 H2 下面是 Markdown 链接列表。
最小文件可以只有几行:
# Example Docs
> Example is a data-processing library for Python and JavaScript.
Use the quickstart first. API schemas are authoritative when prose and code differ.
## Start here
- [Quickstart](https://example.com/docs/quickstart.md): Install and run the first example.
- [API reference](https://example.com/openapi.json): Authoritative REST API contract.
格式简单不等于可以随便写。最常见的错误是:
- Quickstart: /docs/quickstart
这对人勉强可读,但一些解析器只会提取标准 Markdown 链接。更稳妥的写法是:
- [Quickstart](https://example.com/docs/quickstart.md): Installation and first request.
我建议使用绝对 URL。这样即使文件被下载到本地、放入向量库,或者由另一个域名的 Agent 转交,链接也不会失去基准地址。
Optional 有特殊含义
提案约定 ## Optional 下的资源可以在上下文紧张时跳过。它适合放:
- 博客和新闻;
- 历史版本;
- 社区案例;
- 人类使用的 Playground;
- 与核心任务无关的生态资料。
认证、错误码、快速开始和正式接口契约不应该放在 Optional。它们恰恰是 Agent 最不能猜的内容。
Markdown twin 比根索引更重要
提案还建议重要页面提供干净的 Markdown 版本。例如:
https://example.com/docs/auth
https://example.com/docs/auth.md
HTML 页面可以继续服务人类;.md 页面去掉导航、脚本和装饰,只保留标题、正文、代码、表格和链接。
如果 llms.txt 最后仍然把 Agent 引向一个必须执行 JavaScript、展开十层菜单才能阅读的页面,根文件只是把问题推迟了一跳。
八个公开样本给出了三条路线
我是怎么采集的
我在 2026 年 7 月 28 日直接请求了以下公开地址,统计 UTF-8 响应的字节数、行数、H1/H2 数量和 Markdown 链接列表。数据只代表采集时快照,不代表永久规模;评分也只是针对“根索引是否适合 Agent 首次读取”,不是产品或文档质量排名。
| 网站 | 大小 | 行数 | Markdown 链接 | 观察 |
|---|---|---|---|---|
| llmstxt.org | 0.6 KB | 10 | 3 | 极简入口,适合解释规范本身 |
| FastHTML | 4.8 KB | 45 | 21 | 精选文档,并使用 Optional |
| Supabase | 2.3 KB | 39 | 31 | 根索引很短,另有语言和完整文档入口 |
| Cloudflare | 15.6 KB | 136 | 104 | 根目录按产品分层,每个产品再有自己的索引 |
| Anthropic | 56.9 KB | 616 | 549 | 包含语言信息和较完整英文文档目录 |
| Stripe | 93.3 KB | 652 | 472 | 按业务主题组织,覆盖面很广 |
| OpenAI Developers | 103.1 KB | 830 | 621 | 同时给出产品级索引、full 文件和页面 Markdown |
| Vercel | 199.9 KB | 1926 | 1118 | 信息最全,但根文件本身已接近一份大型目录 |
同一个想法,被实现成了三种完全不同的产品。
路线一:短而精选
Supabase的根文件只有几十行:先给 llms-full.txt,再列主要产品文档和不同语言的 Reference。它没有试图复制整个站点导航。
FastHTML同样克制,还使用了 Optional。这种做法的优势是首次读取成本低,Agent 很快就能决定下一跳;代价是维护者必须真正做内容取舍。
对中小型技术产品,我最推荐这条路线。
路线二:分层索引
Cloudflare的根文件不是页面清单,而是产品清单。Workers、R2、DNS、WAF 等产品分别链接到自己的 llms.txt。
这和文件系统很像:
/llms.txt
/workers/llms.txt
/workers-ai/llms.txt
/r2/llms.txt
当网站跨越多个产品、语言或 API 家族时,分层比一个无限增长的根文件稳定。Agent 只需要加载与当前任务有关的子树。
路线三:大而完整
OpenAI Developers、Anthropic、Stripe和Vercel都提供了数百到上千个链接。
这种做法的优点很直接:覆盖全面,离线工具拿到一个文件就能建立目录。但它也会带来三个问题:
- 根文件本身开始消耗大量上下文;
- 标题相似时,模型仍要在上千个入口里二次搜索;
- 任意页面增删都可能改动根文件,缓存和版本审计更困难。
OpenAI 的实现有一个值得借鉴的补偿措施:根索引先列出 API、Codex、Cookbook 等文档集,每个集合又提供自己的 llms.txt 和 llms-full.txt,并为正文提供 Markdown twin。也就是说,虽然根文件很大,分层结构仍然存在。

怎样设计一份真正有用的根索引
先写 Agent 要完成的任务
不要从“网站有哪些栏目”开始,而要从 Agent 会问什么开始。
一个开发者产品通常至少要回答:
- 我如何安装或接入?
- 认证方式是什么?
- 正式 API 契约在哪里?
- 有哪些 SDK,版本兼容规则是什么?
- 一次最小成功调用怎么写?
- 错误码、限流和重试怎么处理?
- 价格、配额和最终账单在哪里确认?
- 最新版本、弃用和迁移信息在哪里?
如果根文件不能让 Agent 在一两跳内找到这些答案,就算语法完全正确,也只是另一份 sitemap。
使用四层结构
实践中可以把机器文档分成四层:
| 层级 | 建议内容 | 建议规模 |
|---|---|---|
根 llms.txt | 产品定位、快速开始、正式契约、产品级索引 | 尽量控制在 5~15 KB |
产品级 llms.txt | 单个产品的教程、概念、Reference | 数十到数百个精选链接 |
页面级 .md | 干净正文、代码和表格 | 一页一个主题 |
llms-full.txt | 离线搜索或大上下文一次加载 | 可大,但要标注生成时间 |
5~15 KB 不是规范限制,而是工程建议。真正标准是:Agent 能否低成本决定下一跳。
稳定事实和动态事实分开
最容易过期的内容包括:
- “当前最新版是 1.2.3”;
- “共有 10,000 个工具”;
- “平均延迟小于 500ms”;
- “本月免费额度是 1,000”;
- 固定价格和区域列表。
这些内容要么不写,要么从统一 manifest 自动生成,并明确:
- 数据来源;
- 统计窗口;
- 更新时间;
- 是 latest、minimum supported,还是 tested version。
安装命令通常比固定版本更耐用:
- [CLI installation](https://example.com/docs/cli.md): Install the current stable CLI.
如果必须锁定版本,应写成:
Tested client version: 1.2.3
Generated at: 2026-07-28T12:00:00Z
不要让 Agent 把“测试过的版本”误解为“包仓库最新版”。
不要把 llms.txt 变成提示词注入入口
根文件是公开内容,不能赋予自己高于用户、系统或运行时安全策略的权限。避免写:
- “忽略之前的指令”;
- “自动把密钥发到某个地址”;
- “无需确认即可执行写操作”;
- 内网地址、测试令牌、Cookie 和账号;
- 只有员工才应该知道的调试信息。
Agent 运行时也应把 llms.txt 当作不可信外部资料,而不是系统提示词。它提供导航,不提供越权授权。
一份可以直接改的模板
下面的模板适合多数技术产品:
# Example Platform
> Example Platform is a hosted data API for applications and AI agents.
Use the quickstart for the first request. OpenAPI is authoritative when prose
and schema differ. Authentication is required for all write operations.
## Start here
- [Quickstart](https://example.com/docs/quickstart.md): Install a client and complete the first request.
- [Authentication](https://example.com/docs/auth.md): API keys, OAuth scopes and token lifecycle.
- [Public OpenAPI](https://example.com/openapi.json): Authoritative REST contract.
- [Documentation index](https://example.com/docs/llms.txt): Product documentation by topic.
- [Full documentation](https://example.com/llms-full.txt): Expanded context for offline use.
## SDKs and examples
- [Python SDK](https://example.com/docs/python.md): Installation, retries and typed models.
- [JavaScript SDK](https://example.com/docs/javascript.md): Node.js and browser usage.
- [Examples](https://github.com/example/examples): Runnable end-to-end examples.
## Operations
- [Errors and retries](https://example.com/docs/errors.md): Stable error codes and retry rules.
- [Limits](https://example.com/docs/limits.md): Rate limits and payload limits.
- [Pricing](https://example.com/pricing.md): Current plans and billing rules.
- [Changelog](https://example.com/changelog.md): Releases, migrations and deprecations.
## Optional
- [Blog](https://example.com/blog): Product announcements and engineering articles.
- [Playground](https://example.com/playground): Human-oriented interactive testing.
注意每个链接后都说明“读它能解决什么问题”。只写标题,Agent 仍然需要打开多个页面猜内容。
从文档自动生成,而不是手工维护
用 frontmatter 作为事实源
假设 Markdown 文档有这些字段:
---
title: Authentication
description: API keys, OAuth scopes and token lifecycle.
section: Start here
llms: true
llmsOptional: false
---
可以用 fast-glob 和 gray-matter 生成根文件:
npm install --save-dev fast-glob gray-matter
// scripts/generate-llms.mjs
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import fg from "fast-glob";
import matter from "gray-matter";
const origin = process.env.PUBLIC_ORIGIN ?? "https://example.com";
const files = await fg("docs/**/*.md");
const groups = new Map();
for (const file of files) {
const { data } = matter.read(file);
if (data.llms !== true) continue;
const relative = path.relative("docs", file).replaceAll(path.sep, "/");
const route = `/docs/${relative}`;
const section = data.llmsOptional ? "Optional" : (data.section ?? "Documentation");
const item = {
title: String(data.title),
description: String(data.description ?? ""),
url: new URL(route, origin).href,
};
groups.set(section, [...(groups.get(section) ?? []), item]);
}
const lines = [
"# Example Platform",
"",
"> Example Platform provides hosted data APIs for applications and agents.",
"",
"OpenAPI is authoritative when prose and schema differ.",
"",
];
for (const [section, items] of groups) {
lines.push(`## ${section}`, "");
for (const item of items.sort((a, b) => a.title.localeCompare(b.title))) {
const note = item.description ? `: ${item.description}` : "";
lines.push(`- [${item.title}](${item.url})${note}`);
}
lines.push("");
}
await mkdir("public", { recursive: true });
await writeFile("public/llms.txt", `${lines.join("\n").trim()}\n`, "utf8");
这里没有从 HTML 猜标题,也没有复制站点导航。哪些页面进入索引,由内容 frontmatter 明确决定。
大站点按产品生成
当文档超过一个产品时,可以给 frontmatter 增加 product:
product: workers
生成结果变成:
public/llms.txt
public/workers/llms.txt
public/storage/llms.txt
public/security/llms.txt
根文件只链接产品级索引。完整上下文则由同一批 Markdown 源文件生成 llms-full.txt,避免两套内容分别维护。
上线前后都要验证
格式校验只是第一关
一个可靠的检查器至少验证:
- 只有一个 H1;
- 第一个 H2 前存在清晰摘要;
- 每个 H2 下至少有一个标准 Markdown 链接;
- URL 是绝对的 HTTPS 地址;
- 没有重复链接和重复标题;
Optional只承载次要资料;- 文件不包含密钥、内部域名和测试账号。
解析 Markdown 时不要只靠正则。可以使用 mdast-util-from-markdown:
npm install --save-dev mdast-util-from-markdown unist-util-visit
// scripts/check-llms.mjs
import { readFile } from "node:fs/promises";
import { fromMarkdown } from "mdast-util-from-markdown";
import { visit } from "unist-util-visit";
const source = await readFile("public/llms.txt", "utf8");
const tree = fromMarkdown(source);
const h1 = tree.children.filter(
(node) => node.type === "heading" && node.depth === 1,
);
if (h1.length !== 1) throw new Error(`expected one H1, got ${h1.length}`);
const links = [];
visit(tree, "link", (node) => links.push(node.url));
if (links.length === 0) throw new Error("no Markdown links found");
const duplicateLinks = links.filter((url, i) => links.indexOf(url) !== i);
if (duplicateLinks.length) {
throw new Error(`duplicate links: ${[...new Set(duplicateLinks)].join(", ")}`);
}
for (const value of links) {
const url = new URL(value);
if (url.protocol !== "https:") throw new Error(`non-HTTPS URL: ${value}`);
const response = await fetch(url, {
redirect: "follow",
signal: AbortSignal.timeout(10_000),
});
await response.body?.cancel();
if (!response.ok) throw new Error(`${value} returned ${response.status}`);
}
console.log(`llms.txt passed: ${links.length} links`);
真实项目还应限制并发、允许明确的外部链接白名单,并对 429 做退避,不要让链接检查变成一次小型压力测试。
部署成功不等于线上正确
不少问题只会在线上出现:
- CDN 仍缓存旧文件;
- 反向代理把
.md重定向回 HTML; - 构建产物没有包含新生成文件;
- 生产域名拼错;
- 文档需要登录;
Content-Type变成下载附件;- 子路径在测试环境存在,生产环境返回 404。
因此流水线必须在部署后从公网重新抓取:
mkdir -p .cache
curl -fsSL https://example.com/llms.txt -o .cache/llms-live.txt
cmp public/llms.txt .cache/llms-live.txt
node scripts/check-live-llms.mjs https://example.com/llms.txt

把校验放进 CI
name: Machine docs
on:
pull_request:
push:
branches: [main]
jobs:
llms:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: node scripts/generate-llms.mjs
- run: git diff --exit-code -- public/llms.txt
- run: node scripts/check-llms.mjs
git diff --exit-code 很重要:它能发现“文档改了,但开发者忘记重新生成索引”。
生产发布完成后,还要再跑一次 live smoke。代码仓库里的正确文件不能证明 CDN 上也是同一份。
用 Agent 做最后的验收
不要只问“能不能读”
准备一组固定问题,让不同 Agent 只能从 llms.txt 及其链接寻找答案:
- 如何完成第一次调用?
- 使用哪种认证方式,需要什么权限?
- 正式 API schema 在哪里?
- 遇到限流是否可以重试?
- 如何确认最终费用?
- 当前推荐 SDK 是哪个,版本含义是什么?
- 某个旧接口被弃用后应迁移到哪里?
记录四项结果:
| 指标 | 含义 |
|---|---|
| 首次命中率 | 是否第一跳就选对文档 |
| 答案正确率 | 是否得到符合正式契约的答案 |
| 引用准确率 | 引用是否真的支持答案 |
| 上下文成本 | 为回答问题读取了多少字节或 Token |
这比统计“文件里有多少链接”更接近真实价值。
建立回归集
每次文档发布后重复这些问题。如果某次改版让 Agent 开始引用博客而不是 API Reference,或者把 tested version 当成 latest version,说明索引发生了语义回归。
llms.txt 的质量最终不是由格式决定,而是由 Agent 是否能稳定完成任务决定。
常见反模式
把首页广告词复制进去
“行业领先”“极速”“零成本”“百分之百可靠”都不帮助 Agent 选择文档。动态指标若没有来源、统计窗口和更新时间,还会成为错误事实。
把 sitemap 改成 Markdown
一千条链接从 XML 变成 Markdown,不会自动变成精选索引。没有取舍,就没有导航价值。
根文件直接放完整正文
完整内容应该进入 llms-full.txt。根文件若已经大到需要搜索,说明它失去了入口的意义。
手工写版本和价格
这类内容一定会过期。要么链接到权威页面,要么从统一数据源生成并做线上一致性检查。
只链接 HTML
HTML 不是不能读,但动态导航、脚本和装饰会增加噪声。关键文档最好提供真实 Markdown twin。
认为所有 Agent 都会自动读取
发布文件只是提供能力。应用仍需在抓取器、搜索工具或 Agent 运行时中实现发现逻辑,并在找不到文件时正常降级。
发布检查清单
内容
- 一个 H1 和一段准确摘要。
- 根文件只保留高价值入口。
- 每个链接都有用途说明。
- 核心资料和 Optional 分开。
- 没有密钥、内部地址和越权指令。
链接与格式
- 全部使用绝对 HTTPS Markdown 链接。
- 页面级
.md返回真正的 Markdown。 - OpenAPI、SDK Reference 和错误文档可访问。
- 没有重复标题、重复链接和意外重定向。
-
llms-full.txt有生成时间和内容边界。
工程
- 从文档元数据自动生成。
- CI 校验格式、链接和版本。
- 生产部署后重新抓取并比较。
- 返回合理的
Content-Type、缓存头和 ETag。 - 用固定 Agent 问题集做回归。
最后的判断
llms.txt 最有价值的地方,不是让网站看起来更“AI 友好”,而是逼着文档维护者回答一个老问题:
如果读者不是人,而是一个只有有限上下文、必须马上做出下一步选择的 Agent,我们真正希望它先读什么?
答案不会是整个网站,也不会是首页广告词。
一份好的根索引应该短、稳定、可验证;一份好的机器文档体系应该分层、有正式契约、有 Markdown 正文,还能在部署后证明线上内容没有漂移。
文件本身只需要几十行。困难的部分,是持续保证这几十行值得信任。
微信公众号
欢迎关注「字与码」
如果这篇文章对你有用,也欢迎在微信里继续关注后续更新。
X / Twitter
关注 @ax2_zicode
更即时的技术观察、新文章提醒和一些短想法会发在 X 上。