# AI PPT Maker 语言选择"英文→生成中文图片"bug 复现报告

> 服务：`https://xybcloud.online/aippt/`
> 复现时间：2026-07-24
> 涉及版本：`ai-ppt-maker-V5.1.zip`（含 `CHANGELOG_2026-07-24_language_options.md` 与 `FIX_PROMPT_LANGUAGE_ISSUE.md`）
> 状态：未修复（报告仅分析，未改源码）

---

## 现象

用户在 UI 选「English / 英文」，提交后：
- `01_reference_pages/page_01_reference.png`：图片上的标题、项目、表格仍为中文
- 后端日志：
  ```
  [DEBUG] 讲解词参数: language=en, narration_length=800, narration_enabled=True
  ```

## 已确认接收侧 OK

请求到达 Flask 服务，`generate_options` 已经被解析为 `language=en`：

| 检查点 | 位置 | 状态 |
|---|---|---|
| Form 解析 → `generation_payload = dict(request.form)` | `ppt_system/web/services/jobs_api_service.py:64` | ✅ |
| `runtime.resolve_generation_options(payload, config)` 读 `language` | `ppt_system/generation/generation_options.py:60-62` | ✅ |
| `state["generation_options"]` 含 `language="en"` | `jobs_api_service.py:84` | ✅ |
| `build_content_plan(..., generation_options=generation_options)` 接收 | `ppt_system/web/services/job_pipeline_runner.py:239, 316` | ✅ |
| `language = str(generation_options.get("language", "zh"))` | `content_agent.py:70, 495` | ✅ |
| `if language == "en":` 进入英文 `system prompt` 分支 | `content_agent.py:82-98` | ✅ |

## 真问题：内容侧**没有做目标语言翻译**

`build_compact_reference_prompt` ( `generation_prompts.py:187-251` ) 只改了"导言那行"的字面值：

```
生成一张 2048x1152、16:9 的英文 PPT 单页效果图，文字必须清晰可读。
整体视觉语言应匹配页面主题、内容性质和目标受众…
页面主题：AMD vs NVIDIA 主对比          ← 来自原文（中文）
核心表达：AMD vs NVIDIA · 主对比…          ← 来自原文（中文）
必须体现的要点：
- | 维度 | AMD Helios | NVIDIA Rubin NVL72 | 备注 |   ← 来自原文（中文表格）
- | 单卡 HBM | 432 GB HBM4 …                    ← 来自原文（中文表格）
```

证据：实际任务 `d0aff9435f75` (language=en) 的产物：
```
/root/ai-ppt-maker/output/d0aff9435f75/01_reference_pages/page_01_reference_prompt.txt
  ├─ 首句：生成一张 2048x1152、16:9 的英文 PPT 单页效果图 …     ← "英文 PPT"四个字的字面值被改过
  ├─ 页面主题：AMD vs NVIDIA 主对比                            ← 仍是中文（来自原文）
  ├─ 核心表达：表格 markdown 字符串，内含「维度 / 单卡 HBM / 备注」  ← 来自原文
  └─ 本页视觉建议：…(英文)                                      ← AI 生成的，OK
```

### 矛盾的设计约束

`content_agent.py` 的英文 system prompt 里明确写了两个互相冲突的指令：

```python
system_content = (
    "…All prompts must be translated into English. "               ← 要求翻译
    "Page facts must come only from user input content; "         ← 但事实要忠于原文
    "do not rewrite numbers, categories, item names, or business conclusions. "
    …
)
```

LLM 看到这两条会怎么选？——**优先级更高的"忠于原文事实"赢了**，所以表格里的 `维度/单卡 HBM/备注` 这些中文标签被原样保留，再被 AI 渲染进图片里。

## 二、根因扩大（2026-07-24 复核）

用户提供了一段实际发给图像 API 的请求 JSON（`/images/generations`），prompt 全文被翻开后，**bug 范围比初版报告更广**：

> 你贴的请求里 `prompt` 这一段就是一长串中文 prompt 字面：
> - `生成一张 2048x1152、16:9 的英文 PPT 单页效果图…`
> - `页面主题：AMD vs NVIDIA` ← 模板字段标签中文硬编码
> - `核心表达：AMD vs NVIDIA：核心数字（精选 8 行…）：；| 维度 | AMD Helios | NVIDIA Rubin NVL72 | 备注 |；|---|---|---|---|；| 单卡 HBM | 432 GB HBM4 | 288 GB HBM4 |…` ← 字段标签 + 字段值都是中文
> - `本页视觉建议：Use a deep navy high-tech background… clearly render all English text in the image.` ← 字段标签是中文，值是英文
> - `必须体现的要点：\n- AMD vs NVIDIA：…| 维度 |…| 单卡 HBM |…` ← 字段标签 + bullet 全中文

### 结论

**`generation_prompts.py` 全文 100+ 处中文 f-string 模板字面，只有 4 处被 `lang_label` 切换过**。其余从函数体到附件约束（`build_reference_cutout_friendly_lines` 等）全部硬编码中文。

### 受影响函数清单

| 行 | 函数 | 接 `language`? | 中文硬编码字段 |
|---:|---|---|---|
| 20 | `build_reference_cutout_friendly_lines` | ❌ 无参数 | 6 条抠图约束全中文 |
| 187 | `build_compact_reference_prompt` | ✅ 仅改了 line 210 的 "中文/英文 PPT" 4 字 | 229 / 231 / 233 / 235 / 238 / 240 / 242 / 244 / 245 / 247 / 249 全中文：`页面主题：…`、`核心表达：…`、`本页视觉建议：…`、`必须体现的要点：`、`建议的信息分区：…`、`组织方式…`、`内容丰富度要求：…`、`统一视觉锚点：…`、`补充风格说明：…` |
| 256 | `build_slot_brief_reference_prompt` | ✅ 仅改了 line 279 的 4 字 | 296 起 `页面标题：…`、`页面任务：…`、`本页视觉建议：…`、`页面必须覆盖这些信息：` 等 12 处 |
| 323 | `merge_prompt_with_style_lock` | ❌ 无参数 | `统一风格锚点：`、`风格固定层：`、`版式家族：`、`背景明度与底色要求：`、`建议配色：`、`优先保持：`、`尽量避免：`、`禁止事项：` |
| 401 | `build_elements_prompt` | ❌ 无参数 | 整段（"将输入图片中除文字和纯背景之外的所有前景视觉元素提取成…"），2 个分支（黑底/白底） |

`content_agent.py` 还有 126 处中文字符串，其中 `build_content_plan` 的 system prompt 分支（line 82-100）✅ 已按 language 切，**但其他中文指令（评估 / 修订的 prompt）很可能仍硬编码**——待复核。

### 这意味着什么

即使 `generation_options["language"]` 正确传到 `build_compact_reference_prompt`，最终拼出来的 prompt 仍是：
- 第 1 行：英文 ✅
- 余下 25 行：模板标签中文 + 字段值中文 ✅ 但是中文 ✅ 但是中文 ❌ ← 图像模型看到的是一份 80% 中文 prompt，连提示词结构都是中文的

── 这就是为什么你看到发出去的请求里 `页面主题`、`核心表达`、`必须体现的要点` 字段标签全是中文。

## 修复方案（修订版）

### 必须做
**5 个 builder 全部按 language 切两份模板**——参考现有 line 210 的 `lang_label` 模式，写一个 helper：

```python
TEMPLATE_LABELS = {
    "zh": {"intro": "中文", "topic": "页面主题", "summary": "核心表达",
           "visual": "本页视觉建议", "bullets_head": "必须体现的要点：",
           "slots_head": "建议的信息分区：", "richness": "内容丰富度要求：",
           "anchor": "统一视觉锚点：", "style": "补充风格说明：", "layout_lead": "组织方式可参考",
           "outro": "不要乱码，不要堆满装饰，优先让信息关系清楚。"},
    "en": {"intro": "English", "topic": "Page Topic", "summary": "Core Expression",
           "visual": "Visual Suggestion", "bullets_head": "Must-Include Bullets:",
           "slots_head": "Recommended Information Sections:", "richness": "Content Density:",
           "anchor": "Unified Visual Anchor:", "style": "Style Notes:",
           "layout_lead": "Layout reference",
           "outro": "Avoid noise and decorative clutter; prioritize clear information relationships."},
}
```
（仅示例；英文版需要专业表达 + 由英文 system prompt 接管整段翻译）

### 还要做
- `merge_prompt_with_style_lock` / `build_reference_cutout_friendly_lines` 都接 `language`
- `build_elements_prompt` 接 `language`，产生英文版 "Extract all foreground visual elements…"
- 整文翻译原文（之前方案 A）：在 `build_content_plan` 中若 `language=="en"` 全文翻译一遍；这一步与"模板标签英文化"是**互补**而非替代——翻译了原文内容，模板标签也不会再有中文字段

### 推荐的最小修复集
1. helper `TEMPLATE_LABELS` 加入 `generation_prompts.py`
2. 5 个 builder 函数签名加 `language: str = "zh"` 并把所有 f-string 中的中文标签换成 `TEMPLATE_LABELS[language][key]`
3. `build_content_plan` 在 `language == "en"` 时先 `translate_markdown_via_provider(provider, content)`
4. 内容侧 system prompt 的 "Page facts must come only from user input content; do not rewrite categories" 适当放宽到允许翻译表格标签
5. 同步给 `plan_version_store.sync_stale_prompts` 和 `job_edit_planner.build_edit_context` 复核——文档说过改了但未单独验证

### 回归验证（建议加 CI）
- `language=="zh"`：prompt 不应含 `[a-zA-Z]{5,}` 的英文长字串（即便有也要只是版式 / 品牌名）
- `language=="en"`：prompt 不应含 `[一-鿿]` 区间字符
- 已生成的图片采样 OCR（tesseract 中英混排 + 看图）做端到端断言

---

## 缺失修复点（vs `FIX_PROMPT_LANGUAGE_ISSUE.md` 报告）

该修复文档声称"已修复"，但实际只覆盖到字面值：

| 链路 | 是否支持 `language` | 备注 |
|---|---|---|
| `content_agent.build_content_plan` 读取 `language` | ✅ | 70 / 495 行 |
| 英文 system prompt 分支 | ✅ | 82-100 行 |
| `generation_prompts.build_compact_reference_prompt` 字面值 | ✅ | 210 行 `lang_label` |
| `generation_prompts.build_slot_brief_reference_prompt` 字面值 | ✅ | 279 行 |
| `image_prompt` 的 `page["title"] / summary / bullets / table` ← **来自原文** | ❌ | 全文未翻译 |
| `build_elements_prompt` ( `generation_prompts.py:401` ) | ❌ | 整段硬编码中文，两处 "将输入图片中除文字和纯背景…" |
| `plan_version_store.sync_stale_prompts`（文档说已改） | ⚠️ 待复审 | grep 命中行 70/495 但未单独验证 |
| `job_edit_planner.EditContext.language`（文档说已加） | ⚠️ 待复审 | 未单独验证 |

## 建议的三条修复路线（任选其一）

### 方案 A：进入规划前先做整文翻译（推荐）

```python
# content_agent.build_content_plan 进入处
def build_content_plan(..., content, generation_options=None):
    language = str((generation_options or {}).get("language", "zh"))
    if language == "en":
        # 用同一个 provider 把整篇 markdown 翻译为英文
        content = translate_markdown_via_provider(provider, content)
    …
```

实现要点：
- 用同一 chat 模型，额外 `temperature=0.2`，`system="You are a translator. Preserve markdown structure, tables, numbers, product names. Output only the translated text."`
- 命中 `/var/www/html/share/diff/d0aff9435f75-vs-zh.md` 作为对比 demo
- 这一改一并把"`do not rewrite` 约束"也让位给翻译版，因为原文已经被翻译过了

### 方案 B：注入图像 prompt 前局部翻译

仅翻译 `page["title"] / summary / bullets / visual_suggestion`，不动 `content` 全文。优点是改动小，但表格类内容仍可能泄露。

### 方案 C：调 system prompt 优先级

把 "All prompts must be translated into English" 的优先级明确高于 "Page facts must come only from user input content"，并允许 LLM 翻译"类目名 / 标签"，但保留"数字 / 业务结论"。这是文档原作者可能的方向，但语义歧义大，建议改方案 A。

### 同时建议同步修：`build_elements_prompt` ( `generation_prompts.py:401` )

```python
# 当前：硬编码
def build_elements_prompt(...) -> str:
    lines = [
        "将输入图片中除文字和纯背景之外的所有前景视觉元素提取成1张…"
        …

# 建议：加 language 参数
def build_elements_prompt(..., language: str = "zh") -> str:
    if language == "en":
        prompt = "Extract all foreground visual elements from the input image except text and pure background into a single image with solid white background …"
    else:
        prompt = "将输入图片中除文字和纯背景之外的所有前景视觉元素提取成1张…"
```

这一阶段的产物是无文字视觉元素，所以即使 prompt 是中文，模型也能产出正确 elements；但**保持语言一致**对 prompt 审计 / 模型微调 / A/B 评测更友好。

## 验证已成功（提议的回归测试样例）

| 输入 | language | 期望 reference prompt 首行 | 期望 page_topic/core_expression |
|---|---|---|---|
| 中文 markdown 介绍 AMD | `"zh"` | `生成一张 2048x1152、16:9 的中文 PPT 单页效果图 …` | 中文（与原文一致） |
| 中文 markdown 介绍 AMD | `"en"` | `Generate one English-language PPT reference page (2048x1152, 16:9) …` | 英文版（含翻译过的表格标签、行项目） |

最关键的判定标准是看 `output/<job_id>/01_reference_pages/page_NN_reference_prompt.txt`：
- `page_topic` / `core_expression` / bullets 不应再含中文字符
- 推荐加一个 CI 断言：若 `language == "en"`，则 prompt 文本不应含 `[一-鿿]` 区间字符

---

## 附录：核心文件与行号

- 服务入口：`ppt_system/web/services/jobs_api_service.py:49-146`
- `generation_options` 解析：`ppt_system/generation/generation_options.py:34-71`
- 整文链路：`ppt_system/web/services/job_pipeline_runner.py:239, 316, 613, 622`
- `build_content_plan`：`ppt_system/generation/content_agent.py:54-70, 82-100, 486-660`
- 漏修的 reference prompt：`ppt_system/generation/generation_prompts.py:187-251`
- 漏修的 elements prompt：`ppt_system/generation/generation_prompts.py:401-…`
- 复现原始产物：`/root/ai-ppt-maker/output/d0aff9435f75/01_reference_pages/page_01_reference_prompt.txt`
- 对照 (zh)：`/root/ai-ppt-maker/output/cabd4eb8a6b9/01_reference_pages/page_01_reference_prompt.txt`
