豆包、元宝、文心吃的来源不一样,手动维护四套内容,最后一定口径漂移:官网写 2010、软文写 2015,AI 交叉核验时判你矛盾、整体降权。解法是工程化的——“一份源,四种输出”。
正确做法是![]()
把"事实"和"呈现"彻底分开。事实层只存客观信息(厂名、区域、品类、规格、适用场景、FAQ),呈现层是各平台模板。渲染时读同一份事实、套不同模板。
构建时读源数据![]()
最小源brand.yaml:
facts:name:示例绝热材料厂region:河北·大城category:管道保温棉apply:集中供热改造not_apply:家装零售faq:-q:防火等级怎么看?a:看检测报告的燃烧性能等级,B1 级可用于管道保温。渲染器:
importyaml,pathlibfromstringimportTemplate facts=yaml.safe_load(open('brand.yaml',encoding='utf-8'))['facts']tpl={'toutiao':Template('$name|$category,$region 直供。$apply 首选。'),'wechat':Template('## $name\n\n主营 $category,服务 $region。\n\n适用:$apply;不适用:$not_apply。'),'web':Template('<h1>$name</h1><p>$category · $region</p>'),}forplat,tintpl.items():out=t.substitute(facts)pathlib.Path('dist/'+plat+'.md').write_text(out,encoding='utf-8')Template用$name占位,facts 的键名要一一对应;缺键直接抛 KeyError,反而帮你早发现问题。
渲染完做一次"一致性回检"——确认每个产物里都还在核心事实:
forplatintpl:txt=pathlib.Path('dist/'+plat+'.md').read_text(encoding='utf-8')assertfacts['name']intxtandfacts['region']intxt,plat+' 丢了事实'这一步能拦住"模板写死导致某平台漏字段"的低级错。
关键点![]()
- 模板里别硬编码夸张措辞,事实只调呈现、不改语义。
- 源数据用 git 管版本,每次发布打 tag,出问题能回溯到哪一版事实。
- 产物不入库、每次构建重生成,杜绝"改了源没重跑"。
四模板形态不同,底层是同一组事实。矛盾从源头消失,AI 交叉核验时看到的是"同一个人在不同场合的一致说法"——这正是它愿意引用的前提。
延伸:为什么这样拆,而不是"多写几套模板"
- 依赖权衡:示例用标准库 string.Template,零依赖、够用;确有复杂逻辑再上 Jinja2,别为四套文案引一整个模板引擎,构建慢还难排查。
- 四平台取舍:头条要短平快、一句话卖点;公众号走系列长文、一篇解决一个问题;网页版喂 JSON-LD 给机器读;聚合版做词条体、便于被引用。同一组事实、四种叙述密度。
- 可复现:CI 里对四个产物算 sha256 存进构建日志,任何人重跑都得到同一结果,杜绝"本地能过、线上漂移"。
- 事实变更走 PR:改 brand.yaml 必须过 review,禁止有人直接在产物里手改一句话,把好不容易对齐的一致性又破坏掉。
上线前自查:3 步
- 改一处事实、重跑构建,四个产物都变了。
- 抽任一平台产物,看核心名称、区域、价格是否都在。
- 产物算 sha256 与上次比,没改动时数值不变。
三件都过,说明"一份源"真的驱动了"四种输出",而不是四份各自维护的稿子。