llms.txt:给 Agent 的网站说明书
原创 · 约 42 分钟阅读 · 阅读 --

llms.txt:给 Agent 的网站说明书

作者: 字与码

AI 工程

古董级程序员,从大厂到创业公司,现在还在一线做 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 链接过去
MCPAgent 如何发现并调用运行时工具不适合,它是运行协议
AGENTS.md代码 Agent 在仓库里应遵循什么规则不适合,作用域是代码仓库
llms-full.txt可一次加载的完整文档上下文不能替代根索引,体积可能很大

一套成熟的机器文档不是只放一个文件,而是让这些入口互相配合。

网站文档通过 llms.txt 分流到 Markdown、OpenAPI 和完整上下文

Agent 实际需要的是一条可信路径

理想路径应该足够短:

/llms.txt
  -> 快速开始或 Agent 接入说明
  -> 某个产品的 llms.txt
  -> 页面级 Markdown
  -> OpenAPI / SDK Reference / 示例代码

根文件负责导航,不负责塞进所有答案。页面级 Markdown 负责内容,OpenAPI 负责接口契约,运行时仍然通过 API、MCP 或 CLI 完成调用。

提案规定了什么格式

最小结构并不复杂

根据格式说明,一个符合提案的文件按顺序包含:

  1. 一个 H1,写项目或网站名称。这是唯一必需部分。
  2. 一个可选的引用块,用一小段话概括项目。
  3. 若干不使用标题的补充说明。
  4. 若干 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.org0.6 KB103极简入口,适合解释规范本身
FastHTML4.8 KB4521精选文档,并使用 Optional
Supabase2.3 KB3931根索引很短,另有语言和完整文档入口
Cloudflare15.6 KB136104根目录按产品分层,每个产品再有自己的索引
Anthropic56.9 KB616549包含语言信息和较完整英文文档目录
Stripe93.3 KB652472按业务主题组织,覆盖面很广
OpenAI Developers103.1 KB830621同时给出产品级索引、full 文件和页面 Markdown
Vercel199.9 KB19261118信息最全,但根文件本身已接近一份大型目录

同一个想法,被实现成了三种完全不同的产品。

路线一:短而精选

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 DevelopersAnthropicStripeVercel都提供了数百到上千个链接。

这种做法的优点很直接:覆盖全面,离线工具拿到一个文件就能建立目录。但它也会带来三个问题:

  • 根文件本身开始消耗大量上下文;
  • 标题相似时,模型仍要在上千个入口里二次搜索;
  • 任意页面增删都可能改动根文件,缓存和版本审计更困难。

OpenAI 的实现有一个值得借鉴的补偿措施:根索引先列出 API、Codex、Cookbook 等文档集,每个集合又提供自己的 llms.txtllms-full.txt,并为正文提供 Markdown twin。也就是说,虽然根文件很大,分层结构仍然存在。

精选索引与全量目录对 Agent 上下文的影响

怎样设计一份真正有用的根索引

先写 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-globgray-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

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 及其链接寻找答案:

  1. 如何完成第一次调用?
  2. 使用哪种认证方式,需要什么权限?
  3. 正式 API schema 在哪里?
  4. 遇到限流是否可以重试?
  5. 如何确认最终费用?
  6. 当前推荐 SDK 是哪个,版本含义是什么?
  7. 某个旧接口被弃用后应迁移到哪里?

记录四项结果:

指标含义
首次命中率是否第一跳就选对文档
答案正确率是否得到符合正式契约的答案
引用准确率引用是否真的支持答案
上下文成本为回答问题读取了多少字节或 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 正文,还能在部署后证明线上内容没有漂移。

文件本身只需要几十行。困难的部分,是持续保证这几十行值得信任。

打开原图 ↗