Files
agcore/design/prd/2-模板编译器UTF-8编码修复需求.md
T
徐涛 eb7d23de3d docs: 添加模板编译器 UTF-8 编码修复方案与需求文档
- 新增 PDD 方案(编号 32),覆盖 6 处逐字节强转修复、测试设计与版本升级计划
- 新增 PRD 需求(编号 2),定位模板编译阶段中文乱码根因并明确验收标准
- 添加 .codegraph 目录忽略规则,避免本地数据文件入库
2026-08-03 11:21:50 +08:00

10 KiB
Raw Blame History

PRD:模板编译器 UTF-8 编码修复

状态Draft 作者proposal 日期2026-08-03

1. 核心目标

修复 agcore::prompt::PromptTemplate 模板编译器(src/prompt/template.rs)对非 ASCII(UTF-8)文本的编码破坏问题:当前 6 处 bytes[i] as char 将 UTF-8 字节逐字节强转为 Unicode 码点(等价 Latin-1 解码),导致所有含中文等多字节字符的模板经 compile → render 后输出乱码(mojibake),且该乱码会原样发送给 LLM。本次修复使模板编译器对非 ASCII 文本逐字符正确保留,同时保持 ASCII 模板行为完全不变。

2. 目标用户与场景

用户角色 使用场景 核心诉求
agcore 下游 crate(如 dc-management 使用 PromptTemplate 编译中文 system prompt 模板(validator / collector 采集策略) 模板文本经编译渲染后与原文一致,不产生乱码
agcore 下游 crate 使用 PromptTemplate#if / #each / #raw 块语法,块体内含中文 块内容逐字符正确保留
agcore 维护者 修复后发布 patch 版本,下游可升级 公开 API 不变、ASCII 行为不变、有回归测试保障

3. 问题描述

3.1 现象

dc-management 项目的探索调试日志(log/explore-20260803-013158915066.txt)显示:发送给 LLM 的 system 提示词全部乱码:

ä½ æ¯éä¾ç¥ç¥ä¸å®¶ï¼è´è´£å®¡æ¥å·²é产åç¼ç ç»æï¼å³ç­ä¸ä¸è½®æç´¢æ¹åã

(原文为「你是采集策略专家,负责审查已采集的产品编码结果,决策下一轮搜索方向。」)

日志中的 user 消息与 LLM 响应均为正常中文,证明:乱码发生在内存字符串中(模板编译阶段),实际发送给 LLM 的请求体就是损坏文本;日志文件本身(纯 UTF-8 直写)只是如实记录了已损坏的内容。该缺陷为静默失败——模型在容忍乱码的情况下继续工作,但提示词中的规则约束(编码格式、品牌限定等)已被破坏,直接影响下游采集质量。

3.2 根因

src/prompt/template.rs 共 6 处 bytes[i] as char(将 UTF-8 字节逐字节强转为 Unicode 码点,等价 Latin-1 解码):

行号 位置 破坏内容
255 compile_fragments literal 分支 模板纯文本(主要破坏点
275 parse_tag {{ 标签 }} 内部内容(当前为 ASCII 变量名,未触发;若使用中文变量名同样损坏)
324 parse_block else_body #if 分支的 else 块体
326 parse_block body #if 分支块体
364 parse_each_block body #each 块体
389 parse_raw_block content #raw 块内容

中文 UTF-8 编码每字 3 字节(emoji 等 4 字节),被拆成多个 Latin-1 字符(如 0xE4 → ä0xBD → ½),产生 mojibake。所有经 PromptTemplate::compile → render 链路的非 ASCII 模板文本均被破坏。

3.3 影响面

  • 所有使用 PromptTemplate 且模板含非 ASCII 文本的 agcore 下游项目
  • dc-management 的采集策略(validator)与产品信息采集(collector)两条 system prompt 链路全部受影响
  • 已确认模板文件本身为合法 UTF-8、日志写出链路无转码、发送链路无转码——问题仅存在于模板编译器

3.4 已知线索

  • 下游使用 include_str! 宏加载模板文件(如 dc-management 的 src-tauri/src/llm/prompts.rs),该宏具有编译期 UTF-8 校验:非法编码的模板文件在编译期即报错,故模板文件编码问题不可能存在
  • agcore 仓库 src/prompt/template.rs 当前没有任何测试(无 #[cfg(test)] 模块),本次为模板编译器首次补测试
  • 模板语法标签({{ }}#if#each#raw)均为 ASCII,字节比较判断语法是安全的,无需改动

4. 功能清单

v1 必做

  • 统一修复 6 处 bytes[i] as charsrc/prompt/template.rs 255 / 275 / 324 / 326 / 364 / 389 行):

    • 保持现有 bytes[i] 对 ASCII 语法字符(b'{' / b'}' 等)的字节判断不变
    • 字符累积改为按 UTF-8 字符边界推进:从当前位置取完整字符(如 template[i..].chars().next()),再按该字符的 UTF-8 长度推进索引(i += ch.len_utf8()
    • 效果:非 ASCII 文本(中文 / emoji / 任意多字节字符)逐字符正确保留;ASCII 文本行为与修复前完全一致
  • 补充回归测试src/prompt/template.rs 新建 #[cfg(test)] mod tests):

    • 纯中文模板 compile + render 后与原文逐字符一致(主破坏路径 literal)
    • 中文文本 + {{ var }} 变量插值混合渲染正确
    • 中文文本位于 #if / #each / #raw 块体内时正确保留
    • emoji 等 4 字节字符正确保留
    • 多字节字符紧邻 {{ / }} 标签边界的解析正确性
    • 纯 ASCII 模板渲染结果与修复前一致(向后兼容回归)
    • 标签内部含非 ASCII 内容(中文变量名如 {{ 问候 }}、中文 #if 条件)的解析与渲染正确 ← PM Advisor
  • 版本号更新Cargo.toml version 0.3.60.3.7

v1 可选

  • 无(上述三项即可完整修复)

v2 考虑

  • 无。CHANGELOG 记录暂不做(本次需求明确暂不记录),后续版本若需要可单独补充

非目标

  • 不重构模板语法(标签体系、块结构保持现状)
  • 不新增模板功能(新标签、新渲染特性)
  • 不改动公开 APIPromptTemplate::compile / render 签名与语义不变)
  • 不修改其他模块(已扫描 as char / from_utf8_lossy 等模式,项目中无同类编码风险点)
  • 不涉及下游 dc-management 是否回切模板链路的决策(由下游另行评估)

5. 边界与假设

边界 / 假设 来源 说明
ASCII 语法判断可保留 语法分析 模板标签均为 ASCII{}#/>),bytes[i] 字节比较对 ASCII 安全
非 ASCII 文本必须逐字符保留 需求确认 修复目标是编译输出与模板原文一致
ASCII 模板行为零变化 需求确认 向后兼容是硬约束,需回归测试保障
公开 API 不变 需求确认 compile / render 签名不变,下游无需改代码
修复范围 6 处统一 用户确认 「尽可能全面的修复」,包括 parse_tag 内的变量名内容
测试纳入本次范围 用户确认 用户明确要求补充测试
版本为 patch0.3.7 用户确认 bug 修复走 patch 版本,尽快让下游升级
CHANGELOG 暂不记录 用户确认 本次发布不更新 CHANGELOG

6. 术语表

术语 定义 说明
mojibake 乱码 文本因编码解码不匹配产生的字符错乱,此处为 UTF-8 字节被按 Latin-1 逐字节解码
Latin-1 ISO-8859-1 单字节编码 0x000xFF 每个字节对应一个 Unicode 码点;u8 as char 等价于该解码
len_utf8() Rust 标准库方法 返回 char 的 UTF-8 编码长度(1–4 字节),用于按字符边界推进索引
PromptTemplate agcore 提示词模板引擎 支持变量插值 {{ var }}、条件渲染 #if、循环 #each、原始块 #raw

7. 验收标准

  • 中文模板经 compile + render 后与原文逐字符一致(含纯文本、变量插值混合)
  • 中文位于 #if / #each / #raw 块体内时正确保留
  • emoji 等 4 字节字符正确保留
  • 多字节字符紧邻 {{ / }} 边界时解析正确
  • 纯 ASCII 模板渲染结果与修复前完全一致(以 src/prompt/composer.rs 既有 4 个 ASCII 编译测试为基线全部通过,新测试断言等于预定义期望输出)← PM Advisor
  • 6 处逐字节强转全部消除(grep 模式 as char|char::from 验证)← PM Advisor
  • cargo test 全部通过(含新增测试)
  • cargo clippy 无新增警告
  • Cargo.toml 版本为 0.3.7

8. 风险评估

风险 影响 可能性 应对方向
索引推进改动引入边界回归(模板尾部、空模板、{{ 未闭合) 解析错误或 panic 新增测试覆盖边界;保持 ASCII 判断逻辑不变
parse_tag 内标签内容的解码方式与 literal 不一致 中文变量名场景损坏残留 6 处统一采用同一修复模式
下游对 0.3.7 的升级未验证中文模板 下游继续受影响 tag 描述中明确修复内容与最小验证步骤(编译含中文的模板对比输出)← PM Advisor
未来新增模板代码时再次引入逐字节强转 同类 bug 复发 回归测试(中文断言)可捕获此类问题

9. 发布计划

阶段 范围 说明
v1 6 处修复 + 回归测试 + 版本号 0.3.7 本 PRD 范围
发布 打 tag v0.3.7,推送 origin 实际发布动作由维护者执行
发布说明 git tag -a v0.3.7 的 tag 描述作为发布说明载体(附最小验证:编译含中文的模板并对比输出)← PM Advisor 不更新 CHANGELOGtag 描述随仓库分发
下游升级 dc-management 升级 agcore 引用至 v0.3.7 下游自行评估是否回切模板链路

10. 历史版本

版本 日期 变更说明
v1 2026-08-03 人工种子(原始)

种子内容

发起方:dc-management 项目排查探索调试日志 log/explore-20260803-013158915066.txt,发现发送给 LLM 的 system 提示词乱码。

原始需求描述(一字不改):

「检查一下当前日志 log 目录中保存的日志文件,似乎现在发送给LLM的提示词存在乱码?」

根因:

  • src/prompt/template.rs 中 6 处 bytes[i] as char255 / 275 / 324 / 326 / 364 / 389 行)将 UTF-8 字节逐字节强转为 Unicode 码点(等价 Latin-1 解码),非 ASCII 模板文本被破坏
  • 日志文件、发送链路、模板文件编码均无问题,乱码发生在内存字符串(模板编译阶段)

修复方向(用户确认):

  • 6 处统一修复:保持 ASCII 语法判断不变,字符累积按 UTF-8 字符边界推进
  • 补充回归测试(中文模板恒等、混合插值、块内中文、emoji、边界、ASCII 回归)
  • Cargo.toml 0.3.6 → 0.3.7,打 tag v0.3.7
  • CHANGELOG 暂不记录