Research note / 2026/06/24

AI 生成文本格式错乱

AI 生成文本格式错乱修复技术笔记

AI 生成文本格式错乱封面

适用场景

前端接收大语言模型(LLM)流式输出的 Markdown 内容时,经常出现:

  • 加粗标记缺失或错位
  • 标题格式不规范
  • 列表项粘连
  • 空行过多或过少
  • Markdown 结构被破坏导致渲染异常

本文提出一套可落地的三层保障方案:

Prompt 约束 → 前端确定性修复 → 用户交互兜底

核心目标是:不依赖模型输出完美 Markdown,而是保证最终渲染结果稳定可控。


1. 常见问题

错误类型AI 原始输出期望效果孤立加粗标记节拍18 · 椿的意图**去除无效标记加粗范围错乱代价一:共鸣过载。 内容仅标题加粗分点粘连代价一…残象。代价二…污染自动拆分段落标题缺空格##物理法则## 物理法则列表缺换行发生了。 - 残星会理念换行后再渲染列表空行过多连续多个空行压缩为一个空段落


2. 根因分析

2.1 LLM 的统计生成特性

模型按 Token 逐步预测,并不保证 Markdown 语法严格闭合。

典型问题:

  • ** 未成对出现
  • 列表中途断开
  • 标题格式丢失

2.2 Prompt 遵循不稳定

即使 System Prompt 已规定格式,长文本生成过程中仍可能遗忘规则。

2.3 上下文稀释

随着对话长度增加:

  • 早期格式约束权重下降
  • 格式错误概率上升

2.4 多格式任务冲突

同一模型可能在一次回复中混合:

  • 对话
  • 列表
  • 表格
  • 代码块

从而造成 Markdown 结构混乱。


3. 整体解决方案

┌─────────────────────────────┐
│ 第一层:Prompt 约束         │
│ 降低格式错误发生概率        │
└─────────────┬───────────────┘
              ↓
┌─────────────────────────────┐
│ 第二层:前端自动修复        │
│ 确定性算法兜底              │
└─────────────┬───────────────┘
              ↓
┌─────────────────────────────┐
│ 第三层:用户交互辅助        │
│ 格式化按钮 + 手动编辑       │
└─────────────────────────────┘

其中第二层是整个方案的核心。


4. 第一层:Prompt 设计

4.1 格式铁律模板

建议在 System Prompt 中统一注入:

【格式铁律】

- 使用 Markdown。
- 所有标题独占一行。
- 标题符号(## 等)后必须有一个空格。
- 关键术语使用 **加粗**。
- 并列内容必须使用列表。
- 列表项独占一行。
- 段落之间保留一个空行。
- 禁止连续多个空行。

4.2 提供正例

## 世界观

世界的驱动力是**基础频率**。

- **代价一:共鸣过载。** 超出阈值会残象化。
- **代价二:残骸污染。** 长期暴露会记忆流失。

实践证明,明确规则 + 正例通常比单纯规则效果更稳定。

4.3 动态场景提示

根据业务模块追加上下文:

【当前场景】世界观编辑

所有核心概念首次出现必须加粗。
并列关系必须使用列表结构。

5. 第二层:前端自动修复(核心)

设计原则

不要尝试让模型永远正确。

而是保证:

无论模型输出什么,都能被修复成可渲染结构。

5.1 fixMarkdown()

主要职责:

  1. 修复孤立加粗标记
  2. 标题自动补空格
  3. 分点自动换行
  4. 列表自动换行
  5. 空行压缩
function fixMarkdown(text: string): string {
  if (!text) return ""

  let s = text

  const count = (s.match(/\*\*/g) || []).length

  if (count % 2 !== 0) {
    const lastIdx = s.lastIndexOf("**")
    const before = s.slice(0, lastIdx)

    if (/[\w\u4e00-\u9fff]$/.test(before)) {
      s = before + "**" + s.slice(lastIdx)
    } else {
      s = before + s.slice(lastIdx + 2)
    }
  }

  s = s.replace(/^(#{1,6})([^\s#])/gm, "$1 $2")

  s = s.replace(
    /([。!?\n])(\*\*(代价|原因|步骤|规则|提示)[^:*]*:)/g,
    "$1\n$2"
  )

  s = s.replace(/([。!?\n])(-\s)/g, "$1\n$2")

  s = s.replace(/\n{3,}/g, "\n\n")

  return s.trim()
}

推荐进一步增强

增加:

  • 代码块闭合检测
  • 表格列数校验
  • URL 自动转链接
  • 中英文标点统一

6. Markdown 渲染策略

推荐流程:

原始文本
   ↓
fixMarkdown()
   ↓
renderMd()
   ↓
HTML
   ↓
页面展示

关键原则

不要依赖单个大正则。

采用:

逐行解析 + 状态机

优势:

  • 容易维护
  • 容易扩展
  • 对流式输出更友好

7. 流式输出最佳实践

每次收到 Chunk:

const fullText = previousText + chunk

const html = renderMd(fullText)

messageElement.innerHTML = html

优点:

  • 实现简单
  • 修复逻辑统一
  • 几 KB 文本性能完全足够

如需优化:

  • requestAnimationFrame 节流
  • 50~100ms 批量刷新

8. 用户交互兜底

8.1 一键格式化

const handleFormat = () => {
  setText(fixMarkdown(text))
}

8.2 复制原文

建议同时提供:

  • 复制格式化版本
  • 复制原始 Markdown

方便导入其他编辑器。

8.3 可编辑模式

推荐数据流:

Markdown

HTML

contentEditable

htmlToMarkdown()

保存

这样可以兼顾:

  • 富文本编辑体验
  • Markdown 数据存储

9. 进阶优化方向

9.1 统一工具库

lib/
└─ markdown-utils.ts

集中维护:

  • fixMarkdown
  • renderMd
  • htmlToMarkdown

避免多个组件重复实现。

9.2 规则配置化

const rules = [
  fixHeading,
  fixBold,
  fixList,
  fixTable,
]

便于动态扩展业务规则。

9.3 JSON 替代 Markdown

对于高度结构化数据:

{
  "title": "世界观",
  "items": []
}

前端直接渲染。

这是最稳定的方案。

9.4 AI Markdown 校正器

未来可引入轻量模型专门负责:

错误 Markdown

Markdown Fixer

合法 Markdown

作为 fixMarkdown 的增强版。


10. 实践结论

仅依赖 Prompt 无法彻底解决格式问题。

生产环境推荐采用:

Prompt 约束
    +
前端确定性修复
    +
用户交互兜底

三层架构。

其中:

  • Prompt 负责降低错误率
  • fixMarkdown 负责保证可渲染
  • 用户工具负责处理极端情况

这样即使模型输出存在缺陷,也能确保最终展示结果稳定、可维护、可扩展。

Discussion

评论

评论暂未开放

站点尚未配置 GitHub Discussions。文章阅读不受影响,也不会再发起无效的 GitHub 登录请求。