跳到主要内容

你的 Agent 读不懂的生产日志

· 阅读需 10 分钟
Tian Pan
Software Engineer

你把事故响应 agent 接入了 Splunk。你在系统提示里给了它查询语法,给了它执行 SPL 的工具,还有一个新鲜的 API token。第一次真正处理告警时,它拉了错的日志,总结了错的服务,信誓旦旦地报了错的客户。集成做得完美无缺,agent 却一文不值。

你忘了什么。十五年的日志惯例、没文档的字段名、跨越三次重组从 ERR 漂移到 error 再到 ERROR 的告警级别字符串、把 customer_id 在认证服务里变成 cust_id_v2_actual、在计费服务里变成 tenant.user.id 的团队特定后缀——这些东西没有一条出现在 prompt 里。你给了 agent 对 API 的访问权,但你没有给它访问那些让 API 变得有用的机构知识的权力。

这种失败的形状比 Splunk 大得多。任何把查询语言暴露给 agent、而底层语料是团队手工塑造了十年的工具,都会撞上这堵墙。Agent 拿到了动词,没拿到名词。

有访问权,不等于能用上

"agent 能调这个工具"和"agent 知道该问什么问题"之间有一个相变。大多数生产集成只走到第一步,然后假设第二步是顺带免费的。

不是。典型 MCP 服务器上的工具文档只发布名字、描述、输入 schema。这足够让模型知道怎么格式化调用,但不够让它知道该问什么。Schema 告诉你 query 是字符串,它不告诉你索引里有哪些字段、哪些字段填得稳、哪些字段在 2021 年就废弃了但仍出现在 30% 的记录里,也不告诉你哪个团队拥有那条"级别 4 表示警告——除了在遗留 ingestion 管道里它表示致命"的惯例。

这正好是一半的集成。你交付的那一半是 API 契约——调用形式、参数类型、响应格式。你跳过的那一半是语料契约:数据里实际有什么、它的字段是什么意思、一个可被回答的问题长什么样。

Text-to-SQL 团队多年前就撞过这堵墙,并给它起了名字。即使最强的模型,在真实企业 schema 上的复杂查询准确率也会跌到 77% 以下,绝大多数失败都可以追溯到缺失的语义上下文:列的含义、业务定义、合法的过滤值、那种社交意义上存在但在外键图里不存在的关系。在同一个底层数据库上,加一层显式编码这些上下文的语义层,可以把准确率拉过 99%。模型没变聪明。是语料变得可读了。

日志比 SQL 更糟糕。SQL 的 schema 至少有带类型的列。日志是带嵌入式 JSON 的自由文本——是某个人 2019 年半结构化了一下就忘了收尾的东西。

隐性知识藏在你的技术栈里

走进真实事故时的 on-call 频道,看会发生什么。一个资深工程师 8 秒钟就敲出一条 Splunk 查询。里面有四个你在任何文档里都没见过的字段名。它对一张你根本不知道存在的查找表做 join。它根据一个魔法字符串过滤——那是一个已废弃服务的名字,数据管道为了向后兼容还在给行打这个标签。

那条查询是十年模式匹配的产物。这个工程师是通过写 4000 条查询、被骂错、慢慢累积出"我被问到的问题"和"在这套语料上真正回答它的 SPL"之间的私人映射,才学会的。没人把那张映射写下来。它存在于他和另外三个人的脑子里,其中两个已经离职了。

Meta 上个月发了一份内部分析,讲他们尝试把一个 AI 编码助手从事故模式匹配扩展到更广的开发任务时发生了什么。第一类任务系统能跑,因为模式已经被标注过——事故报告、根因、修复 diff。第二类任务它失败了,因为没人标注过那些惯例。两种配置模式对同一个逻辑操作用了不同的字段名。废弃的枚举值必须留着,因为序列化兼容性悄悄地依赖它们。团队找到了五十多个非显然的隐含中间命名惯例,公司里没有任何文档描述过它们。

你的日志看起来就是这样。不是糟糕工程的产物,而是你团队曾承受过的每一次发布压力沉积下来的地质层。Agent 看不见地质。它看到的是一个平面,并假设手册里写的字段就是数据里填充的字段。

工具表面里应该有什么

如果你接受"访问权只是集成的一半",那另一半也有名字:语料契约。它是工具表面的一等公民,和调用形式并列。

一个有用的日志查询工具的语料契约,包含模型不然就得猜的那些东西:

  • 可被查询的 schema 描述。 不是正式的索引定义。是被标注过的那一份——哪些字段存在,哪些填得稳,哪些是稀疏的、为什么,哪些在不同服务里意思不同,哪些是遗留下来的化石。标注本身才是价值所在。
  • 样例问题库。 二十到五十对"自然语言问题"和"在这套语料上真正回答它的 SPL 查询"。这就是把 text-to-SQL 准确率拉上去的同一个把戏:在上下文里给模型本地的方言示例,而不是让它去发明一个。挑那些故意触发字段名陷阱的例子。
  • 魔法字符串词表。 服务名、环境标签、级别值、租户标识符——这些在数据里以原始字符串形式出现的东西。Agent 不会从"生产"猜出 "prod-us-east-1-legacy"。告诉它。
  • 不在索引里的查找和 join。 如果工程师每次都跨表查 cmdb_hosts.csv 拿到团队 owner,那次查找就是答案的一部分。把它做成一个可调用的工具,或者直接内联进去。
  • 反模式。 "不要用 host 字段做服务归属问题的过滤,那字段 2022 年就被弃用了——用 service.owner。" 负面例子很便宜,效果非常好。

这里的模式,和 Splunk 现在为自己的 LLM 集成所固化的模式是同一个:get_splunk_fields 拿到某个 source type 的字段发现,get_splunk_lookups 暴露查找表,以及在工具定义里加入对每个索引的描述,详细写明它的字段和值。微软的 Azure Copilot 可观测性 agent 也明确警告:调查的准确率取决于你的应用是否发出了完整的遥测、保留了关联字段、附带了足够的服务上下文。各家厂商正在收敛到同一个答案:工具表面必须发布"数据叫什么",而不只是"怎么查它"。

没人想做的那次审计

试试这个练习。挑出你 on-call 轮值里事故中最常被问的十个问题。对每一个,让一位资深工程师把他会跑的那条查询逐字写下来。然后让一位初级工程师只用你写下来的文档和 wiki 写出同一条查询。

那个 gap,就是隐性知识债。也几乎正好,就是 agent 会掉进去的那个 gap。

发现会让人不舒服。你会发现真正装着客户 ID 的那个字段上个季度改过了,仪表板还能用,只是因为有人加了一个 coalesce。你会发现"errors"在一个服务里指异常,在另一个服务里指失败的健康检查。你会发现资深工程师做 join 时用的那张查找表,是某人家目录里的一个 CSV,从一个 2023 年退役的系统里手动导出来,需要谁记起来时才刷新一次。

你大部分可观测性栈,跑在这种知识上。仪表板能用,是因为造它们的人记得那些 workaround。Runbook 能用,是因为写它们的人就是读它们的人。这些东西没有一样能熬过一个把 schema 文档当真的全新模型。

你不可能在这种语料之上直接交付一个 agent,而不先把隐含的东西显式化。审计就是工作本身。Agent 只是把它逼出来的那股力。

把"语料怎么叫这些东西"当成上下文

一旦你接受语料有它自己的词表,架构上的转变就跟着来。Agent 的上下文窗口不只是给指令和推理用的,它也是给它要操作的那份数据的本地方言用的。

具体说,这意味着几件事。系统提示或检索层带着一份活的 schema 标注,版本受控,数据模型变化时被复审。样例问题库就放在查询工具旁边,而不是另开一个没人更新的 wiki。魔法字符串词表是仓库里的一个文件,由拥有数据的那个团队拥有,改动走 review。当有人加新服务或重命名字段时,语料契约是这个 diff 的一部分。

这比把工具接起来要多得多的活。这就是教训。接线只要一个下午。语料契约要一个季度,而且它永远做不完——这没关系,因为数据本身也永远做不完。把它当成集成的运行成本,就像你把数据库迁移当成 schema 驱动应用的运行成本一样。

在 agent 化可观测性上赢的团队,不是用了最强模型或最酷 MCP 服务器的那些,而是已经做过审计的那些。是那些资深工程师已经在半文档化本地方言的——因为他们已经被给每一个新人解释这些东西解释烦了。是那些语义层一开始就是为人造的、现在被复用给机器的。

Agent 不是新东西。Agent 是那个吵闹的客户,最终逼你把你最资深的工程师一直留在脑子里的东西写下来。为语料构建,而不只是为 API 构建。当 agent 不再需要猜你的数据叫什么时,集成才开始真正工作。

References:Let's stay in touch and Follow me for more thoughts and updates