AI 给设备编了个保修期:WarrantyPromise 缺失引发的答案漂移排查与修复
做制造业 B2B 官网、产品中心、售后政策页的运维和前端,尤其是页面早就上线、但从没往里塞过结构化数据的那批站点。 如果你已经被销售同事追问过「客户说 AI 回答我们保修三年,这哪来的」,这篇能照着一步步做。 需要你能改模板里输出的 JSON-LD,或者至少能把一段 script 塞进页面 head。
客户拿着一份三十多万的报价单来压价,甩过来一段 AI 助手的回答,白纸黑字写着:这家厂的注塑机整机保修三年,核心部件五年。我们实际政策是整机一年,螺杆和料筒这类核心部件两年,随机型还有差异。凭空多出来的两年不是销售记错了,是 AI 自己补上去的。这事儿最后查下来,根因就一句话:产品页里没有任何保修相关的结构化数据,官网对保修这件事在机器眼里是失语的。
事情是怎么被发现的
三月第二周,华东区销售在周会上提了一句,说最近客户砍价的理由变统一了,都拿 AI 助手的回答当依据。我们当时没当回事,以为是话术。到第三周,五个人里有四个反映同样的问题,两周内一共九个客户提到"你们保修三年",销售总监把聊天记录甩到技术群里,才引起重视。

我们的保修政策一直写在《售后服务手册》里,一份 32 页的 PDF,挂在官网下载中心。整机一年、核心部件两年、易损件三个月,第 14 页有一张按机型分的表格。官网产品页上有参数表、有图片、有"联系我们",就是没有保修期。下载的 PDF 还是设计部用 InDesign 导的,文字层是扫描图,复制不出内容。
销售那边更麻烦:客户拿着 AI 的答案来要价,销售要么当场否认(客户不信,觉得厂家想赖账),要么认下来(公司真亏钱)。这事儿拖了三周才定位到技术侧,白费了不少口舌。
先确认 AI 到底读到了什么
我的做法是先把"AI 说了什么"固定成可复现的记录,而不是凭印象吵架。列了五个问法,覆盖客户最可能问的几种表述,每天上午十点在三个不同的 AI 搜索入口跑一遍,连跑六天,把回答里的数字和它给出的引用来源抄进表格。
问法大概是这几类:「XX 注塑机保修多久」「XX 品牌注塑机售后服务政策」「XX 机型螺杆保修几年」「注塑机整机保修一般多长时间」「XX 设备买了之后保修包含人工吗」。
| 问法 | 引擎 A 的口径 | 引擎 B 的口径 | 引擎 C 的口径 | 引用来源 |
|---|---|---|---|---|
| 品牌+保修多久 | 三年 | 三年 | 未给数字,给链接 | 行业论坛帖、经销商站 |
| 品牌+售后政策 | 三年,部件五年 | 未回答 | 一年 | 经销商站、官网首页 |
| 机型+螺杆保修 | 五年 | 三年 | 三年 | 论坛帖 |
| 行业通用保修 | 三年 | 三年 | 两至三年 | 行业媒体文章 |
| 是否含人工 | 未回答 | 含人工 | 未回答 | 经销商站 |
15 个格子(5 问法 × 3 引擎)里,跟我们真实政策(整机一年)对得上的只有 2 个,剩下的数字要么凭空出现,要么互相打架。更麻烦的是「三年」这个数字出现频率高,反复出现之后它就成了事实上的口径。
这里有个容易被忽略的点:AI 不是只在回答我们品牌的问题时错。问「注塑机整机保修一般多长时间」这种不带品牌的行业问题,答案也是三年。也就是说,行业里有个模糊的先验在那儿摆着,我们官网没提供明确数字,AI 就拿这个先验来填我们品牌的空。
机制:生成式引擎在没有权威锚点时会干什么
这一节讲清楚底层机制,不然后面的改造只能照抄,出问题不知道往哪儿查。
结构化数据在这条链路上的位置:抓取网页并解析正文、把正文切成若干片段、按问题检索相关片段、把片段塞进上下文让模型生成答案。结构化数据在这条链路上扮演的是「权威锚点」的角色:它不参与检索打分的主要计算,但一旦被解析出来,就会作为高置信度的事实条目,优先于普通正文片段进入上下文。
flowchart TD
A[用户提问 保修多久] --> B[检索层 取回 N 个片段]
B --> C{官网有没有结构化保修字段}
C -->|有| D[WarrantyPromise 作为高置信度事实]
C -->|没有| E[只剩论坛 经销商 行业媒体片段]
D --> F[生成层 直接引用官网口径]
E --> G[多个来源数字冲突]
G --> H[模型做软投票 并按同品类先验补全]
H --> I[输出 三年 或 五年 答案漂移]
F --> J[输出 一年 与政策页一致]
冲突消解靠的是软投票,官网连票都没投
冲突消解这一步值得单独说。多个来源给出不同数字时,模型不是挑一个最可信的丢掉其余,而是做一次加权:来源数量、页面更新频率、域名在品类里的出现频次都会计入。我们吃亏就吃在这儿——官网 PDF 压根没被解析成片段,论坛里讨论保修的帖子有十几条,经销商站页面多且常年更新,票数上一边倒。同品类先验则是模型训练时吸收的行业统计印象,「注塑机三年」这种说法在语料里本身就很常见,官网不说话,它就默认按这个印象补。
PDF 里写了,等于没写
PDF 又是另一层坑。不少爬虫会跳过 PDF,或者解析后只拿到一段没有结构的纯文本。我们的保修条款在第 14 页的表格里,就算被解析,切片之后也大概率是「……螺杆 24 个月……」这种脱离主语的碎片,模型很难把它跟「整机 12 个月」区分开。所以「政策写在 PDF 里」在 AI 搜索语境下约等于没写。
WarrantyPromise 能表达的边界
WarrantyPromise 是 schema.org 里的一个结构化值类型,专门用来描述保修承诺,挂在 Product(以及 Offer)下面。它能表达的字段不多,但正好覆盖我们踩的坑。
| 字段 | 期望类型 | 我们填的值 | 填的时候要注意 |
|---|---|---|---|
| warranty | WarrantyPromise | 整机保修节点 | Product 下挂保修承诺的入口,早期文档里也出现过 warrantyPromise 的写法,现在按 warranty 走 |
| durationOfWarranty | QuantitativeValue | value=12,unitCode=MON | 单位别写「年」这种中文字符串,用 UN/CEFACT 的 MON,或用 ISO 8601 的 P12M |
| warrantyScope | WarrantyScope | PartsAndLaborWarrantyScope | 枚举值,说明保的是零部件、人工,还是两者都保 |
| description | Text | 与政策页正文逐字一致 | 这条最容易被改漏,正文改了这里没改就又漂移 |
有个边界要提前认清:一个 WarrantyPromise 节点只能表达一档政策,也就是一个时长配一个范围。我们有整机一年、核心部件两年两档,硬塞进一个节点必然丢一半,所以第二档拆到 additionalProperty 里用 PropertyValue 表达。别把两档排成数组指望解析器自己挑,它会各取一半拼出「整机两年」这种更离谱的说法。
改造前:页面里那段 JSON-LD 长什么样
改之前,产品页模板输出的是这么一段。下面两段代码块里的 // 行是说明用的注释,落到线上模板时记得删掉,标准 JSON 不接受注释。
// 改造前 product.html 输出的那段 JSON-LD
// 环境:Hugo 0.121 + 自研 product 模板,字段全部硬编码在模板里
// 问题:整段里没有任何保修字段,机器读到的产品不含保修信息
{
"@context": "https://schema.org",
"@type": "Product",
"name": "UN320A 伺服节能注塑机",
"sku": "UN320A-SV", // 型号编码,与 ERP 里的物料码对齐
"brand": { "@type": "Brand", "name": "示例机械" },
"category": "注塑机",
"description": "锁模力 3200kN,伺服节能,适用于家电外壳与日用品注塑成型。",
"offers": {
"@type": "Offer",
"priceCurrency": "CNY",
"availability": "https://schema.org/InStock"
// Offer 里也没有 warranty,整棵树都查不到保修
}
// 到这里就结束了,后面没有别的节点
// 没有 warranty,也没有 hasMerchantReturnPolicy
// 保修信息只存在于 /download/after-sales-2024.pdf 这份扫描版 PDF 的第 14 页
}
这段代码语法上挑不出毛病,校验器也不报错。问题在于它只回答了「这是什么产品」,没回答「保修多久」,而后一个问题恰恰是 B2B 采购第一轮就问的。
改造后:把保修写成机器能直接抄的字段
// 改造后:保修字段从 product front matter 读,不再硬编码
// 环境:Hugo 0.121,字段来源 content/product/un320a.md 的 warranty 段
// 要点:数字、单位、范围三者齐全,缺任何一个都可能被解析器丢弃
{
"@context": "https://schema.org",
"@type": "Product",
"@id": "https://example.com/product/un320a#product", // 稳定实体 ID,跨页引用时用得上
"name": "UN320A 伺服节能注塑机",
"sku": "UN320A-SV", // 与改造前保持一致,便于对比
"brand": { "@type": "Brand", "name": "示例机械" },
"category": "注塑机",
// 第一档:整机保修 12 个月,含零部件与上门人工
"warranty": {
"@type": "WarrantyPromise",
"name": "整机保修",
// durationOfWarranty 用 QuantitativeValue,unitCode 取 UN/CEFACT 的 MON(月)
"durationOfWarranty": {
"@type": "QuantitativeValue",
"value": 12,
"unitCode": "MON"
},
// warrantyScope 用 WarrantyScope 枚举,PartsAndLabor 表示零部件与人工都保
"warrantyScope": "https://schema.org/PartsAndLaborWarrantyScope",
"description": "自设备验收合格之日起 12 个月,含零部件更换与上门人工。"
},
// 第二档:核心部件 24 个月,WarrantyPromise 一个节点只放一档,多档用 PropertyValue 拆开
// 拆开的原因:塞进同一个节点会让解析器各取一半,拼出「整机两年」这种更离谱的说法
"additionalProperty": [
{
"@type": "PropertyValue",
"name": "核心部件保修期",
"value": 24,
"unitCode": "MON", // 24 个月,同样用 MON 不用「年」
"description": "螺杆、料筒、液压系统自验收合格之日起 24 个月。"
},
{
"@type": "PropertyValue",
"name": "易损件保修期",
"value": 3,
"unitCode": "MON",
"description": "加热圈、密封圈等易损件自验收合格之日起 3 个月。"
}
// 三档政策拆成三个节点,而不是塞进一个 WarrantyPromise
],
"offers": {
"@type": "Offer",
"priceCurrency": "CNY",
"availability": "https://schema.org/InStock"
// 这里不再重复声明保修,避免两处数值打架
}
}
有三处是刻意这么写的:@id 给实体一个稳定标识,后面做跨页引用和实体对齐时用得上;durationOfWarranty 走 QuantitativeValue 而不是裸字符串,解析器拿到的是「12 + MON」这种带单位的结构,不会把「12」理解成 12 年;description 跟政策页正文保持逐字一致,两边一旦有出入,答案漂移会换个形式再出现。
政策页正文也得跟着改成可抓取的
只加 JSON-LD 不够。结构化数据负责让引擎拿到准确数字,但如果政策页正文本身是图片或者只有 PDF 链接,正文层面的证据还是空的,引擎在需要引用原文时照样没东西可抄。我们的做法是把 PDF 里的条款原样落成一个 HTML 页面,用 dl/dt/dd 写条款,让「术语—定义」成对出现。
<!-- 售后政策页 layouts/support/warranty.html -->
<!-- 环境:Hugo 0.121,替代原先只有 PDF 下载链接的页面 -->
<!-- 原则:正文里的数字必须与 JSON-LD 里的值完全一致 -->
<section id="warranty-policy">
<h1>整机与核心部件保修政策</h1>
<!-- 用 dl/dt/dd 写条款,机器能按术语-定义成对取到内容 -->
<dl>
<dt>整机保修期</dt>
<dd>自设备验收合格之日起 12 个月。</dd>
<!-- 核心部件单独成条,不跟整机混在一句话里 -->
<dt>核心部件保修期</dt>
<dd>螺杆、料筒、液压系统自验收合格之日起 24 个月。</dd>
<dt>易损件保修期</dt>
<dd>加热圈、密封圈等易损件自验收合格之日起 3 个月。</dd>
</dl>
<!-- 别把政策做成图片,图片在机器眼里是零内容 -->
<!-- PDF 保留,但标注它是历史版本,避免旧版本继续被当权威来源 -->
<p>
<a href="/download/after-sales-2024.pdf" rel="nofollow">
2024 版售后服务手册(PDF,存档版本)
</a>
</p>
</section>
PDF 我没删,只是给它标了「存档版本」并加了 rel="nofollow"。删掉下载链接会惹恼老客户,但让旧 PDF 继续被当成权威来源也不行,这个折中处理实际跑下来没有副作用。
上线后的观测和验证
改完不等于结束。我们加了一条 CI 检查,模板一变就校验线上 JSON-LD,同时把原来那五个问法继续每天跑,观察口径收敛情况。
sequenceDiagram
participant D as 开发改模板
participant CI as CI 流水线
participant S as 线上站点
participant M as 每日问法巡检
D->>CI: 提交 product.html 变更
CI->>CI: 校验 JSON 合法性与 warranty 字段断言
CI->>S: 校验通过才允许发布
S->>M: 发布后第二天起重新跑 5 个问法
M->>M: 记录数字与引用来源
M->>D: 口径漂移则回滚并排查
# 环境:bash + Node 18,跑在 git push 之后的 CI 钩子里
# 依赖:curl 8.x、jq 1.7
# 思路:把「JSON-LD 有没有保修字段」变成一条会红的断言,而不是靠人记着
# 第一步:抓线上页面,把 JSON-LD 抠出来存临时文件
curl -s https://example.com/product/un320a \
| sed -n 's/.*application\/ld+json">\(.*\)<\/script>.*/\1/p' > /tmp/ld.json
# 第二步:校验 JSON 合法性,这一步能挡住注释漏删导致的语法错
jq -e . /tmp/ld.json > /dev/null && echo "JSON 合法"
# 说明:jq -e 在断言失败时返回非 0,配合 || exit 1 让整条流水线红
# 第三步:断言保修字段存在且数值正确,不满足就让流水线红
jq -e '.warranty.durationOfWarranty.value == 12' /tmp/ld.json || exit 1
# 第四步:断言保修范围枚举没写成空字符串
jq -e '.warranty.warrantyScope | endswith("PartsAndLaborWarrantyScope")' /tmp/ld.json || exit 1
# 第五步:断言单位码是 MON,防止有人改成中文「月」
jq -e '.warranty.durationOfWarranty.unitCode == "MON"' /tmp/ld.json || exit 1
# 第六步:比对政策页正文里的数字,防止两边各说各话
curl -s https://example.com/support/warranty | grep -q "12 个月" || exit 1
# 第七步:把结果写进当天巡检日志,供后续口径核对
echo "$(date +%F) warranty-check ok" >> /var/log/geo-warranty.log
CI 那道断言看着多余,跑了一个月就拦下一次:市场部把政策页正文改成「整机保修 18 个月」做促销,JSON-LD 还停在 12,两边对不上。要是没这道检查,AI 那边立刻会分化出「12 个月」和「18 个月」两个口径,又得从头排查一遍。
连续观测六周的结果:
| 观测项 | 改造前(3 月) | 改造后(4 至 5 月) | 说明 |
|---|---|---|---|
| 15 个答案格子与政策一致数 | 2 | 13 | 剩下 2 个是行业通用问题,本来就不该答品牌口径 |
| 回答引用官网来源的比例 | 0/15 | 11/15 | 引擎开始把官网当权威锚点 |
| 出现「三年」「五年」的次数 | 累计 41 次 | 累计 3 次 | 残余 3 次来自历史缓存的论坛帖 |
| 销售反馈客户质疑保修的次数 | 两周 9 起 | 六周 1 起 | 那 1 起是旧 PDF 被引用 |
数据是我们自己这六周的记录,样本不大,趋势可以信,具体数字别当成行业结论看。
这几个坑别踩
只改 JSON-LD 不改正文是典型的半截工程。结构化数据让引擎拿到数字,正文让引擎能引用原话,缺一个,答案的稳定性就掉一档。
单位写错是最常见的低级错误。durationOfWarranty 里写 "value": 12 却没给 unitCode,或者 unitCode 写成「月」这种中文,解析器要么丢弃要么误判成 12 天。UN/CEFACT 的 MON 是标准写法,别自己发明。
多档保修塞进一个节点会丢信息,前面说过,一个 WarrantyPromise 只表达一档,多档拆开写。
把政策页做成图片是最隐蔽的坑。我们有个竞品把保修政策做成了长图,视觉上挺整齐,机器眼里就是零内容,同样的问题在 AI 搜索里会被放大。
顺带说一句更大的背景
制造业 B2B 的采购决策链条长,客户在接触销售之前会先问 AI,这几年已经成了常态。做生成式引擎优化(Generative Engine Optimization, GEO)这件事,落到工程侧其实就是把产品参数、保修、交期、认证这些「客户一定会问」的事实,用机器能解析的结构写清楚,并且让页面上所有位置的说法保持一致。GEO 跟传统 SEO 的差异在于,它关心的不是排名,而是 AI 回答里那句话是不是你自己的口径。 AI 搜索这块还在变,各家引擎对结构化数据的采信权重也在调,但「官网自己不表态,就会被别人的说法代表」这条短期内不会变。
如果你手上的站点也有类似的空白字段,欢迎在评论区说说你那边被 AI 编出来的是什么参数,我这边遇到过的还有交期和认证编号,都是同一类问题。
参考与延伸
- WarrantyPromise 类型与 durationOfWarranty、warrantyScope 的官方定义:https://schema.org/WarrantyPromise
- Product 类型可用属性清单,保修相关字段在这里查:https://schema.org/Product
- 官方结构化数据校验器,改完先过一遍:https://validator.schema.org/
关键词:GEO、AI 搜索优化、WarrantyPromise、结构化数据、JSON-LD、制造业 B2B、答案漂移