跳到主要内容

源头语义 — 把「是什么意思」写进契约

这一章讲三件事: schema(表结构)和语义(含义)的区别——以及为什么弄混它 会让后面所有工程白做;语义为什么必须在数据进门的那一刻就写下来; 书给出的具体工件:一份事件源协议(ESA)长什么样、七个部分各管什么。 承上:03 章建了实体内核,内核要求「每个属性有书面语义」——这一章讲语义从哪来。

1. 顶层全景:数据进门时,把话说清

数据源(变更流/外部供应商/应用遥测)


┌──────────────────────────────┐
│ 数据契约(进门时签) │ SLA · 表结构 · 就绪信号 ← 传统契约只有这三样
│ + 每个事件的语义 │ ← 本书要求补上的部分
└──────────────────────────────┘


管道加工 → 实体内核 → RAG 应用取数

图说:契约是数据源和平台之间的约定。传统契约管「货什么时候到、包装什么样」;
本书要求它同时管「这批货到底是什么」。

2. schema 不是语义:一个事件,两份说明

这一节讲清全书最容易混的一对词。混了它,后面每一步都会跑偏。

先说来源。平台的数据来自三类地方1:系统记录的变更流(订单改了、合同续了, 一条条流出来)、外部数据供应商、以及遥测——应用把「用户做了什么」自动上报回来的 一条条使用记录。

这些数据进来,首先要有一份契约(数据提供方与平台之间的书面约定)。传统契约写的是 保底交付三件套:服务水平承诺(何时到货)、表结构(schema,数据有哪些列、 各是什么类型、格式有什么约束)、以及「源头已就绪」的信号机制2。书说这些都重要, 但有一整类信息被漏掉了——而它恰恰决定 RAG 应用能不能用2

漏掉的就是语义。书的对照值得原样记住3:

schema(结构)语义(含义)
回答的问题数据长什么形状每一行每一列该作何理解
例子「Action.Type 是一串字符」「Action.Type=OpenSurface 表示用户主动打开了输入面板」
谁能从里面读出来任何人看字段列表就行没人能从字段列表里读出来,只能问当初定规则的人

然后是走查。书用了一个真实事件做例子:CreateCopilot_Intent—— 「用户起了用 Copilot 的念头」这个事件。它的 schema 长这样4:

Event.Id: "b123-456" ← 这次事件的唯一编号
Session.Id: "s789" ← 用户这次会话的编号
Module.Name: "Create" ← 哪个产品模块
Feature.Name: "Copilot.QuickCompose"
Action.Type: "OpenSurface" ← 动作类型
Client.Type: "Web" ← 从哪个端
Device.Name: "SurfacePro"

只看这七行,一个字都不用改就能讲出完全不同的故事。语义文档补上的正是 schema 说不出的那两句5:

  1. 只有当登录用户主动发起(点了「Try Copilot」按钮,或按了快捷键)、 并且输入面板真的加载出来,两个条件同时成立,这个事件才发;
  2. 鼠标悬停、气泡浮现这类被动曝光,不发

这两句话就是语义。它不改变任何字段类型,却决定了一个关键指标怎么数: 「今天有多少人对 Copilot 产生了兴趣」——按第一句,悬停不算数;没有这两句话, 两个团队可以各自合理地数出两个不同的数。02 章三个「用户」的事故, 在字段级别重演了一遍。

3. 为什么必须钉在源头:四个理由

这一节回答:语义为什么不能等、不能靠口口相传、不能让写管道的人顺手记住。

理由一:管道代码的意义取决于数据的意义。 书观察到一个数据工程特有的现象: 有了 AI 编程助手后,管道代码本身好写了,但管道代码的独特之处在于—— 离开对所处理数据含义的理解,代码就没有意义;看懂整条管道, 最终要回到看懂源数据的语义6。03 章的实习生读不懂管道,缺的就是这一块。

理由二:事件的含义会自己漂移。 遥测数据的契约必须和埋点代码的 owner (在产品代码里插入上报语句的开发者)签,原因很实际:应用天天在改, 功能一改,事件的形状和含义可能无意中跟着变;而且由于代码封装和抽象, 含义甚至可能在埋点代码一个字没动的情况下改变7

理由三:不写下来的理解会随人流失。 写管道的工程师为了赶交付, 会把「这列数据其实要排除测试账号」这类理解直接写进代码。书的态度很强硬: 不鼓励任何「契约里没有明文记载其假设」的管道代码——否则代码会演化到 除了原作者谁也看不懂为什么这么写,而原作者迟早调走8

理由四:出了事要能对人解释。 决策者看到指标变动,会要一个解释。 没有源数据语义,数据团队连「数据源哪里变了」都用大白话说清—— 而当年和供应商谈依赖的那批人往往已经不在了,留下全是新人9。 反过来,契约写清了语义,RAG 应用就能同时基于代码和数据推理(把代码里的逻辑和表里的数据放在一起想)9

至于要写多细,书给了一个务实的边界:语义是开放式的大工程,多数团队养不起; 只写「刚好够支撑业务逻辑开发」的那一部分就够了10

4. 主走查:一份事件源协议(ESA)从头到尾

这一节把书给出的完整模板走一遍。它是「源头语义」落成纸面的样子。

书公开了微软叫作 ESA(Event Source Agreement,事件源协议)的模板, 配一份遥测 schema,用于给新数据源上户口11。下面按原文的七个部分走, 走查对象就是 §2 那个点按事件:

第 1 部分 抬头:谁签的、什么版本、怎么改
Owner: Create 团队 | 版本 1.3.0 | 生效 2025-10-15
变更窗口:双周;破坏性变更提前 2 周通知
→ 意义:含义变了必须走版本,不许静默改

第 2 部分 目的(Purpose):一句话钉死语义的边界
「只记录『用户主动发起 Quick Compose 并导向 LLM API 调用』的动作」
→ 意义:§2 的两句语义文档,在这里升格为契约条款

第 3 部分 汇点与新鲜度:数据到哪去、多快到
主汇点 Geneva → 导出到 Cosmos | 新鲜度要求 ≤ 60 分钟
粒度:仅事件级,不允许预聚合
→ 意义:「多快算新鲜」写成了可检查的数字,不再是口头感觉

第 4 部分 规范事件:每个事件一条语义
① CreateCopilot_Intent——语义:登录用户显式发起(点击或快捷键)
且提示面加载完成时发一次;必填字段含 Event.Id、User.ANID、Tenant.Id……
② CreateCopilot_PromptSubmitted——语义:用户提交了触发 LLM 调用的提示词时
发一次;带 CorrelationId(关联标识:把同一次交互的三个事件串起来的编号)、
LLM.Provider、Prompt.LengthBucket
③ CreateCopilot_ResponseReceived——语义:收到 LLM 首个返回词时发一次;
带 Latency.FirstTokenMs、Action.Result(成功/限流/拦截/失败)
→ 意义:§2 走查里那一个事件,在这里变成一条三拍完整的时间线:
起意 → 提交 → 出结果,三拍靠同一个 CorrelationId 串起来

第 5 部分 版本与变更管理
次版本:加可选字段或枚举值(默认 Unknown);主版本:删字段或收紧约束,
要通知并更新 ContractDoctor(书里校验契约一致性的内部工具)
→ 意义:和第 1 部分呼应,「怎么改」本身也是契约

第 6 部分 可测试性与样本
每天抽 200 条事件做协议一致性自动检查;每种规范事件在 CI 里备合成样本
→ 意义:语义不是写了就算,有机器天天对着真数据核对

第 7 部分 所有权与联系人
产品负责人、平台采集接口人、管道接口人,邮箱写明
→ 意义:§3 理由三担心的「人都调走了」,在这里提前布防

再补一步 schema 本身:书给的 JSON 版事件 schema 里,Action.Type 的取值被限定成 OpenSurface/Submit/FirstToken 三个——正好对应规范事件的三个语义角色, 结构文件和语义文件在这里互相咬合12

5. 作者的判断与证据、边界

有制度撑的: §4 整份 ESA 是书里放出的真实模板11;「和埋点 owner 签契约」 「不鼓励契约外假设的代码」是微软内部的成文流程78; 「每个洞察能引到源头」的 GenAI 前实践在 03 章已经引过。

作为判断提出的: 「AI 编程助手让语义文档化从『可选』变『紧迫』」是作者的观察, 书没给对照数据;「RAG 应用能同时基于代码和数据推理」是方向性论断, 具体怎么推理要等未出版的章节。

边界与局限: ESA 的七部分是遥测源的形状——外部供应商数据、数据库变更流的 契约该怎么写,书没有给对应模板;「每天抽 200 条」这类具体数字是微软平台的 体量经验,小团队照抄未必合适;ContractDoctor、Geneva、Cosmos 都是微软内部专名, 读者拿不到,复现要换成自家工具。

6. 可带走的

**全章走查合起来一行:**同七个字段,补上「只有主动发起才发、悬停不发」两句语义, CreateCopilot_Intent 才成为能数的指标;再用一份七部分的 ESA 把语义、 新鲜度、版本、样本、联系人签成契约。

  1. schema 管形状,语义管含义——含义没人能从字段列表里读出来;
  2. 语义要在数据进门当天写进契约,晚了就靠口口相传,人一走就断;
  3. 判断写多细的标尺:够支撑业务逻辑开发即可,不追求穷尽;
  4. 遥测契约必须和埋点代码的 owner 签——应用一改,事件含义会悄悄漂移;
  5. 不许写「契约里没有明文假设」的管道代码;
  6. ESA 七部分:抬头与变更窗口 / 目的 / 汇点与新鲜度 / 规范事件 / 版本管理 / 测试样本 / 联系人;
  7. 规范事件写成带语义的一句话+必填字段,同一次交互的事件用关联标识串起来;
  8. 语义要有机器天天抽真数据核对的机制(采样:每天抽真数据+合成样本),不是写完归档。

7. 原文地图

主题原书章原文位置
三类数据源数据地基:RAG 的语义骨干text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:156(搜「change feeds」)
传统契约三件套数据地基:RAG 的语义骨干text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:160(搜「SLA」)
管道代码的意义取决于数据意义数据地基:RAG 的语义骨干text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:164(搜「meaning of the code」)
schema 与语义之辨数据地基:RAG 的语义骨干text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:166(搜「Schema describes」)
事件 schema 示例数据地基:RAG 的语义骨干text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:168(搜「CreateCopilot_Intent」)
语义两句:主动发起才发、悬停不发数据地基:RAG 的语义骨干text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:181(搜「explicitly initiates」) · text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:183(搜「passive UI exposure」)
只写刚好够用的语义数据地基:RAG 的语义骨干text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:187(搜「just enough semantics」)
与埋点 owner 签约、含义漂移数据地基:RAG 的语义骨干text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:189(搜「instrumentation owner」)
不鼓励契约外假设的代码数据地基:RAG 的语义骨干text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:191(搜「expressly documented」)
向决策者解释变动数据地基:RAG 的语义骨干text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:193(搜「aggregated metrics」)
ESA 模板全貌数据地基:RAG 的语义骨干text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:197(搜「Event Source Agreement」) · text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:209(搜「Freshness Requirement」) · text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:212(搜「Canonical Events」)
版本管理与 ContractDoctor数据地基:RAG 的语义骨干text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:234(搜「ContractDoctor」)
采样与合成样本数据地基:RAG 的语义骨干text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:237(搜「200 sampled events」)
事件 JSON schema数据地基:RAG 的语义骨干text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:248(搜「Canonical Event Base」)

Footnotes

  1. 出处:「数据地基:RAG 的语义骨干」第 156 段(text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:156,搜「change feeds」)。

  2. 出处:「数据地基:RAG 的语义骨干」第 160 段(text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:160,搜「SLA」)。 2

  3. 出处:「数据地基:RAG 的语义骨干」第 166 段(text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:166,搜「Schema describes」)。

  4. 出处:「数据地基:RAG 的语义骨干」第 168-178 段(text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:168,搜「CreateCopilot_Intent」)。

  5. 出处:「数据地基:RAG 的语义骨干」第 181-183 段(text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:181,搜「explicitly initiates」)与(text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:183,搜「passive UI exposure」)。

  6. 出处:「数据地基:RAG 的语义骨干」第 164 段(text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:164,搜「meaning of the code」)。

  7. 出处:「数据地基:RAG 的语义骨干」第 189 段(text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:189,搜「instrumentation owner」)。 2

  8. 出处:「数据地基:RAG 的语义骨干」第 191 段(text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:191,搜「expressly documented」)。 2

  9. 出处:「数据地基:RAG 的语义骨干」第 193 段(text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:193,搜「aggregated metrics」)。 2

  10. 出处:「数据地基:RAG 的语义骨干」第 187 段(text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:187,搜「just enough semantics」)。

  11. 出处:「数据地基:RAG 的语义骨干」第 195-243 段(text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:197,搜「Event Source Agreement」)与(text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:241,搜「Product DRI」)。 2

  12. 出处:「数据地基:RAG 的语义骨干」第 246-267 段(text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:248,搜「Canonical Event Base」)与(text/05-ch02-chapter-2-data-foundations-a-semantic-backbone-f.txt:258,搜「OpenSurface」)。