YAML Frontmatter:文件开头的"元数据层"

4 阅读4分钟

YAML Frontmatter:文件开头的"元数据层"

一、YAML Frontmatter 是什么

YAML 是什么

YAML 是一种数据格式,用来表示结构化数据,和 JSON、XML 同类。

它跟文件类型无关,可以写在 .yaml 文件里,也可以写在程序字符串里。

它靠缩进表示层级,用 键: 值 写字段 ,用 - 写列表:

name: 张三
age: 28
skills:
  - Python
  - Go
address:
  city: 北京

同一份数据,换 JSON 写要括号、引号、逗号:

{
  "name": "张三",
  "age": 28,
  "skills": ["Python", "Go"],
  "address": {"city": "北京"}
}

换 XML 写要成对标签,更冗长:

<person>
  <name>张三</name>
  <age>28</age>
  <skills>
    <skill>Python</skill>
    <skill>Go</skill>
  </skills>
  <address><city>北京</city></address>
</person>

三者表达的是同一份数据,只是写法不同——JSON 用括号引号、机器友好;XML 用成对标签、严格冗长;YAML 用缩进和短横线、最接近人写字,代价是对缩进极其敏感(多一个空格或混进 Tab 就报错)。

Frontmatter

Frontmatter 是一种位置约定:规定"文件开头的两行 --- 之间放元数据",不关心这段元数据用什么格式写。

YAML Frontmatter

YAML Frontmatter 就是两者合起来——用 YAML 格式写的 Frontmatter,是 Markdown 生态里最常见的叫法。

所以严格说,YAML 是格式,Frontmatter 是约定,YAML Frontmatter 是约定 + 格式的组合。你写的文件仍然是 .md,只是开头那段被约定为元数据:

---
key: value
---
正文内容

补充一点:Frontmatter 不一定非用 YAML。Hugo 就支持 YAML、TOML、JSON 三种写法,只是 YAML 是事实标准,所以大家默认说 "YAML Frontmatter"。

事实标准:没有官方组织强制规定,但大家用得多了、慢慢默认都这么做,于是它就成了标准。

二、它用在哪里

核心思路只有一句话:机器先读头部,快速知道这个文件是干嘛的;正文只在需要时才完整读。

AI 技能(SKILL.md)

头部决定这个技能能不能被用上:

---
name: pdf-processing
description: 提取 PDF 文本、填写表单。用户处理 PDF 时使用。
allowed-tools:
  - Read
  - Write
---

# PDF Processing
...
  • description 最关键:工具先扫所有技能的描述,和用户请求匹配,匹配上了才加载正文。
  • allowed-tools 等字段用来控制权限和管理信息。

好处是:技能再多,也不用把所有正文都塞进 AI 的上下文,省空间。

AI 记忆(memory 文件)

头部用来给记忆分类、排优先级:

---
type: feedback
priority: high
created: 2026-10-20
---

用户偏好:单元测试用 pytest,测试文件放 tests/ 目录。

type、priority 让工具决定先注入哪条记忆、后注入哪条。

其他常见场景

  • AI Agent 定义:Claude Code、GitHub Copilot、Gemini CLI 等都用"Markdown + YAML 头部"来定义 agent,头部配模型、工具等参数。
  • Hugging Face 模型卡:头部记录 license、tags、datasets 等,平台靠它分类和筛选模型。
  • 静态网站:Hugo、Jekyll、Docusaurus 早就这么用了,头部写标题、日期、是否草稿,正文是文章。
  • Ansible:用 .meta.yml 文件定义参数规则。

看出规律了吗?头部给机器快速读取,正文给人或模型慢慢看。 这不是 AI 时代的新发明,而是 Markdown 圈用了很多年的老约定,AI 工具只是把它发扬光大了。

三、怎么解析

解析就是"把头部和正文切开",但要处理各种意外:

def parse_frontmatter(text: str) -> tuple[dict, str]:
    if not text.startswith("---\n"):
        return {}, text          # 开头没有 ---,说明没头部
    parts = text.split("---", 2)
    if len(parts) < 3:
        return {}, text          # 格式不完整
    try:
        metadata = yaml.safe_load(parts[1]) or {}
    except yaml.YAMLError:
        return {}, text          # YAML 写错了
    if not isinstance(metadata, dict):
        return {}, text          # 解析出来不是字典
    return metadata, parts[2].lstrip()

四个关键点:

  1. 开头必须是 ---\n,否则直接当普通文本。
  2. 最多切三段:空串、头部、正文。
  3. 任何一步失败都降级:返回空头部 + 原文,保证正文永远能读。
  4. 结果必须是字典,防止 YAML 解析出字符串或列表。

四、新手容易踩的坑

  • 缩进只能用空格,不能用 Tab——这是报错第一大原因。
  • 普通字符串不用加引号,只有含 :、# 等特殊字符时才加。
  • 列表写成 - 项目 换行缩进,不要写成 [a, b] 那种 JSON 风格。
  • 布尔值写 true / false,建议全小写。

五、一句话总结

Frontmatter 就是文件开头的"信息头":机器先读它,快速判断这个文件是什么、要不要用;正文是真正的内容,按需加载。

头部写坏了也不影响正文——这个"降级"设计,就是它好用的关键。