让AI Agent直接读Markdown

网站无需新增一套页面,只要根据 Accept 请求头返回 Markdown,就能让 AI Agent 用更少字节和 Token 获取正文。本文给出内容协商、缓存、发现机制与验证方法的完整实践。
让 AI Agent 直接读 Markdown,网站该改的不是 URL
截至 2026 年 8 月 27 日,越来越多 AI 编程工具开始在抓取网页时主动请求 Markdown,而网站只需在原有 URL 上增加一次 HTTP 内容协商,就能同时服务浏览器和 Agent。浏览器继续得到完整 HTML,支持该协议的 Claude Code、Cursor、OpenCode 等工具则可以拿到结构更干净、Token 消耗更低的 Markdown。
这项改造真正有价值的地方,不是给网站再生成一批 .md 文件,而是让同一个页面地址能够按访问者声明的格式偏好返回不同内容。Roots 公布的实测案例显示,同一篇文档从 HTML 切换为 Markdown 后,响应字节数减少了 84%,进入模型上下文的 Token 数量约缩减至原来的六分之一。对于需要连续读取几十页文档的编码 Agent,这不是微小优化,而是直接影响速度、成本和可用上下文长度的基础设施变化。

核心机制:同一个 URL,两种表示
HTTP 内容协商是服务器根据请求头,为同一个资源选择合适表示格式的标准机制。 当 Agent 请求 /docs/install 并发送 Accept: text/markdown, text/html, */* 时,它表达的意思是:优先返回 Markdown;如果网站不支持,再返回 HTML 或其他可接受格式。
text/markdown 是 Markdown 内容对应的标准媒体类型。 该类型由 RFC 7763 定义,现行 HTTP 语义则由 RFC 9110 统一描述;RFC 7231 仍常被旧文章引用,但已经被 RFC 9110 等文档取代。实现时应返回 Content-Type: text/markdown; charset=utf-8,而不是含糊地标成 text/plain。
内容协商与在 URL 末尾追加 .md 是两回事。 /docs/install 与 /docs/install.md 是两个地址,而根据 Accept 返回 HTML 或 Markdown,是同一个地址的两种表示。前者要求 Agent 预先知道网站自定义的地址规则,后者只依赖通用 HTTP 语义。
这一区别决定了方案能否被自动发现。部分 SEO 插件声称可以“为 AI 提供 Markdown”,实际只是生成额外的 .md 页面;如果原始 HTML 没有通过 Link: rel="alternate" 或页面中的 <link rel="alternate"> 声明替代版本,一个不知道该约定的 Agent 根本找不到它。Roots 对相关插件的测试就发现,Agent 向规范 URL 发送 Accept: text/markdown 时,服务器仍然返回 HTML。
2026 年的支持现状:已经能用,但不能押注全部流量
Markdown 内容协商已经进入主流 AI 编程工具,但尚未成为所有 Agent 的一致行为。 AcceptMarkdown 的兼容性页面和 Checkly 的独立测试都表明,不同工具、版本甚至同一产品中的不同抓取函数,发送的 Accept 值仍然不同。
| 工具或抓取能力 | 已观察到的 Accept 行为 | 能否自动请求 Markdown | 实施影响 |
|---|---|---:|---|
| Claude Code Fetch | text/markdown, text/html, */* | 是 | 可直接命中同 URL 的 Markdown 表示 |
| Cursor WebFetch | 优先声明 text/markdown | 是 | 适合文档、博客与变更日志 |
| OpenCode webfetch | Markdown 权重最高,HTML 权重较低 | 是 | 能识别带 q 权重的协商结果 |
| OpenClaw | 已被兼容性项目列为支持者 | 是 | 仍应按实际版本核验日志 |
| GitHub Copilot 部分抓取能力 | 主要请求 HTML、XHTML | 否 | 仍会获得原始 HTML,不应阻断 |
| Windsurf 部分读取能力 | 仅发送宽泛的 */* | 否 | 服务器应默认返回 HTML |
兼容性表只能反映特定版本和特定时间点,不能代替站点自己的访问日志。 AI 工具更新频率很高,底层抓取器也可能从浏览器环境切换到服务端 Fetch,或者反向调整。因此,生产环境应该记录 Accept 和 User-Agent,用真实请求确认覆盖率,而不是根据产品名称写死判断逻辑。
网站不应该通过 User-Agent 猜测访问者是不是 AI。 User-Agent 容易变化、伪装和复用,同一工具也可能包含多套请求组件;Accept 才是客户端对响应格式的明确声明。按照格式偏好响应,比维护一张不断过期的 Agent 名单更稳定。
收益不只是少几个 HTML 标签
Markdown 的首要收益是减少与正文无关的 Token。 HTML 页面通常包含导航、页脚、按钮、脚本、样式类名、无障碍属性和重复链接,正文可能只占响应的一小部分;Markdown 则可以保留标题、段落、列表、表格和代码块,同时去掉页面组件产生的噪声。
84% 的字节降幅并不意味着所有网站都会得到完全相同的结果。 静态文档站本来就较简洁,压缩比例可能较低;组件繁多的产品博客、CMS 页面和包含大量内联数据的站点,收益往往更高。更可靠的评估方式,是对同一批 URL 分别抓取 HTML 与 Markdown,比较压缩前字节数、压缩后传输量、模型分词数量和正文提取完整率。
| 指标 | HTML 表示 | Markdown 表示 | 应关注的问题 | |---|---:|---:|---| | 响应字节数 | 通常较高 | Roots 案例减少 84% | 是否移除了导航、脚本和重复组件 | | 模型 Token | 包含大量标签和属性 | Roots 案例约减少至六分之一 | 同一上下文窗口能否容纳更多页面 | | 结构可读性 | 依赖 DOM 清洗 | 标题、列表、代码块直接可见 | 表格和嵌套列表是否完整 | | 浏览器体验 | 完整交互与视觉样式 | 不适合作为默认页面 | 默认请求必须继续返回 HTML | | Agent 兼容性 | 几乎普遍支持 | 取决于是否发送正确 Accept | 必须保留 HTML 回退路径 |
Markdown 的第二个收益是降低正文提取的不确定性。 Agent 面对 HTML 时通常需要执行正文识别:判断哪块是文章、哪些链接是导航、折叠区域是否值得保留。这个过程会因页面模板变化而失效;服务器直接输出 Markdown,相当于由内容所有者明确告诉 Agent 哪些信息属于正文。
Markdown 的第三个收益是让代码文档更容易保持语义边界。 代码围栏、参数表、标题层级和步骤列表能够原样进入上下文,减少 HTML 转换器把代码与解释混在一起的情况。对于框架文档、SDK 指南和变更日志,这种结构稳定性通常比纯粹的带宽节省更重要。
实现原则:协商格式,不复制内容
最稳妥的实现是让 HTML 与 Markdown 共享同一个内容源。 如果文章本来以 Markdown、MDX 或结构化 CMS 字段保存,服务器可以直接返回源内容,也可以将其渲染为 HTML;不要维护两份独立正文,否则标题、代码示例和更新时间迟早会分叉。
服务端必须正确解析媒体类型和 q 权重。 简单判断请求头字符串是否包含 text/markdown 在最小实现中能工作,但它会错误处理 text/markdown;q=0,也无法在多个候选格式间比较优先级。生产项目应使用框架现有的内容协商能力或成熟解析库。
下面是一段不绑定具体 CMS 的 Next.js 路由示意,重点是复用内容源、设置响应类型并声明缓存变化维度。它是网站响应逻辑,不是模型 API 调用示例。
import { NextRequest, NextResponse } from "next/server";
import { getDocument, renderDocumentHtml } from "@/lib/content";
export async function GET(request: NextRequest) {
const document = await getDocument("installation");
const preferred = request.headers.get("accept") ?? "text/html";
const wantsMarkdown = preferred
.split(",")
.map((item) => item.trim())
.some((item) => item.startsWith("text/markdown") && !item.includes("q=0"));
if (wantsMarkdown) {
return new NextResponse(document.markdown, {
headers: {
"Content-Type": "text/markdown; charset=utf-8",
"Vary": "Accept",
},
});
}
return new NextResponse(await renderDocumentHtml(document), {
headers: {
"Content-Type": "text/html; charset=utf-8",
"Vary": "Accept",
},
});
}
上面的字符串解析只适合说明流程,正式代码应覆盖完整权重规则。 例如客户端可能发送 text/html;q=1.0, text/markdown;q=0.8,此时 HTML 优先;也可能发送 text/markdown;q=0, */*;q=0.5,此时 Markdown 被明确拒绝。成熟框架通常已有 accepts、content negotiation 或 media type 解析组件,优先使用已有能力。
Vary: Accept 是最容易漏掉的一行
Vary: Accept 用于告诉 CDN 和共享缓存:响应内容会随 Accept 请求头变化。 如果缺少它,CDN 可能先缓存 Agent 请求得到的 Markdown,再把纯文本返回给普通浏览器;也可能先缓存 HTML,导致后续所有 Agent 都拿不到 Markdown。
缓存键的变化会带来命中率成本。 加入 Accept 后,同一 URL 至少可能存在 HTML 与 Markdown 两个缓存变体;如果 CDN 直接把原始 Accept 字符串完整纳入缓存键,不同顺序和不同 q 值还可能制造大量低命中率变体。较成熟的做法是在边缘层先把请求归一化为 html 或 markdown 两类,再建立有限的缓存键。
静态站点同样可以在边缘层完成格式选择。 构建阶段为每篇内容生成 HTML 和 Markdown 两个产物,请求到达 CDN 或边缘函数后,根据 Accept 选择对应文件;对用户暴露的规范 URL 仍保持不变。这样既能避免每次动态渲染,也能保留标准内容协商。
给不发送 Accept 的 Agent 留一条发现路径
Markdown 替代链接是内容协商之外的第二种发现机制。 Vercel 在自己的博客和变更日志实践中,同时使用 Accept: text/markdown、Markdown sitemap 和 rel="alternate",原因很直接:并非所有 Agent 都会主动声明 Markdown,但部分抓取器可以解析替代链接。
HTML 页面可以在 <head> 中声明 Markdown 版本:
<link
rel="alternate"
type="text/markdown"
href="https://example.com/docs/installation"
/>
服务器也可以通过响应头表达相同关系:
Link: <https://example.com/docs/installation>; rel="alternate"; type="text/markdown"
替代链接不一定要求创建新的 .md URL。 当同一个 URL 已支持内容协商时,href 可以仍然指向规范 URL,媒体类型负责说明其 Markdown 表示;如果现有平台无法按请求头切换内容,再把独立 .md 地址作为兼容方案,但要明确声明它与原页面的关系。
Markdown sitemap 是面向批量发现的补充入口。 普通 sitemap 告诉爬虫有哪些页面,Markdown sitemap 则可以进一步标记适合 Agent 消费的内容版本。它适合拥有数千篇文档的大型站点,但不能取代单页内容协商,因为 Agent 很可能直接从搜索结果或用户提供的 URL 进入页面。
不要把网页源码原封不动交给 Agent
Markdown 响应必须是面向阅读的内容表示,而不是 CMS 内部源文件的无条件泄露。 源文件可能含有草稿字段、内部备注、未发布链接、模板指令、组件参数和构建时密钥引用;返回前应经过与 HTML 发布流程同等级别的权限、状态和字段过滤。
动态页面需要先定义什么才是“同一个资源”。 商品库存、账户后台和实时仪表盘并不天然适合转成 Markdown;文档、博客、帮助中心、变更日志和公开知识库则非常适合。第一阶段应该优先覆盖内容稳定、公开且文本密度高的页面,而不是全站统一开启。
Markdown 渲染链还要处理链接、图片和组件降级。 相对链接应在 Agent 当前 URL 下可以正确解析,图片要保留有意义的替代文本,交互式组件则需要转成文字说明或静态数据。只输出一个诸如 <PricingCalculator /> 的 MDX 标签,对 Agent 几乎没有帮助。
提示注入风险不会因为格式变成 Markdown 而消失。 网站返回的文本仍然属于外部不可信内容,Agent 应把它当资料而不是系统指令;网站侧则不应为了“引导 AI”在正文中加入隐藏命令。更干净的格式提升了解析质量,但不等于提升了内容可信等级。
上线前用四组请求验证
验证的目标是确认格式选择、回退、缓存和字符编码都符合预期。 最少需要测试明确请求 Markdown、明确请求 HTML、使用通配符以及带权重的混合请求,同时检查响应头与正文,而不是只看 HTTP 200。
curl -i -H 'Accept: text/markdown' https://example.com/docs/installation
curl -i -H 'Accept: text/html' https://example.com/docs/installation
curl -i -H 'Accept: */*' https://example.com/docs/installation
curl -i -H 'Accept: text/html;q=1, text/markdown;q=0.8' https://example.com/docs/installation
正确结果应该保持 URL 不变,并在响应格式之间稳定切换。 Markdown 请求应得到 Content-Type: text/markdown; charset=utf-8,HTML 请求和无法判断偏好的通配符请求应默认得到 HTML;两类响应都应包含 Vary: Accept。
生产日志必须把 Accept 纳入记录字段。 Nginx 的默认日志通常不记录这个请求头,可以新增专用格式:
log_format with_accept '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'accept="$http_accept"';
access_log /var/log/nginx/access.log with_accept;
监控阶段应重点观察四个数字。 第一是声明 text/markdown 的请求占比,第二是 Markdown 与 HTML 的平均响应字节差,第三是不同表示的缓存命中率,第四是 Markdown 请求出现 4xx、5xx 或格式误判的比例。只有这些数据能判断改造是否真正被 Agent 使用。
这项改造值不值得做
对文档站、开发者平台和公开知识库来说,Markdown 内容协商已经值得进入默认能力清单。 它遵循现有 HTTP 标准,不要求改变规范 URL,也不会破坏浏览器访问;实现成本通常集中在内容源复用、缓存配置和测试,而不是重新建设一套站点。
对只有少量营销页面的网站来说,优先级没有那么高。 如果页面主要依靠视觉展示,正文很短,或者内容完全由客户端运行后生成,Markdown 能节省的 Token 和解析工作都有限。此时先完善服务端渲染、语义化 HTML 和可发现的正文,收益可能更直接。
.md 独立地址可以作为补充,但不应该冒充内容协商。 一个完整方案至少应包括同 URL 的 Accept 协商、正确的 Content-Type、Vary: Accept、HTML 回退、替代版本声明和可观测日志。少任何一项,都可能在 Agent、浏览器或 CDN 其中一端留下不易发现的问题。
这轮变化本质上是网站开始把 AI Agent 当作一种有明确格式偏好的客户端。 过去网站用响应式布局适配手机,用 JSON Feed 或 RSS 适配订阅器;现在通过标准 HTTP 头为 Agent 返回 Markdown,是同一思路的延伸。它没有发明新的抓取协议,却解决了 AI 工具读取网页时最现实的浪费:先下载复杂 HTML,再花 Token 把它还原成原本就存在的正文。
参考来源
- HTTP Working Group 的 HTTP Core 仓库:包含 RFC 9110 等现行 HTTP 语义规范的源文件与勘误记录,可用于核对
Accept、Vary和内容协商规则。 - CommonMark 规范仓库:定义 Markdown 常见语法及测试用例,可用于验证服务端输出的一致性。
- Nginx 官方源码仓库:可用于核对请求头变量、日志格式和响应头配置相关实现。



