别把 LLM 当裸机跑:长文本生成任务中的几个工程化思考
最近在项目里做了一些 LLM 长文本生成的实践,踩了不少坑。本来想直接调通用 API 一把梭,结果发现真正交付的时候问题一大堆。
这篇笔记把我在长文本生成场景下总结的几个工程化思路整理出来,给同样在做类似业务的同行参考。文章里的代码示例都跑过,但具体业务里需要自己适配。
涉及到的技术栈主要是 Python + 主流大模型 API(OpenAI / Claude / DeepSeek 这套),没有偏向任何具体厂商。
一、问题的起点:为什么直接 prompt 写长文不靠谱?
很多人的第一反应是:
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "帮我写一篇 1 万字的 XXX 报告"}],
max_tokens=8000
)
然后期待 LLM 一次性吐出一份高质量的长文。
实际跑下来,至少会撞到这几堵墙:
1. 输出截断
绝大多数模型的单次响应都有上限。即使把 max_tokens 拉满,到了某个 token 数也会被强制截断。真正能稳定生成的连续文本,通常在 2000-4000 字左右,再往上就开始不稳定。
2. 上下文一致性衰减
当生成内容超过 3000 字以后,模型对前文的"记忆"会显著衰减。表现是:术语前后不统一、人物/概念定义飘忽、章节之间的逻辑断层。
这个问题用 attention 可视化可以看得很清楚——前文的 attention 权重会随着距离指数下降。
3. 事实性幻觉
让模型直接生成涉及"客观事实"的内容(数据、引用、案例),模型会以非常自信的语气编造不存在的内容。这在写作类任务里是致命的。
我自己测过,让 GPT-4 写一段关于某个细分行业的报告,里面给出的数据来源、报告名称,70% 是不存在的。模型并不知道自己在胡说,但它会一本正经地把这些信息组装成看起来合理的句子。
二、工程化思路:把"生成长文"拆成"生成短文 + 拼接"
我最后落地的方案是放弃"一次性生成",改成分段生成 + 上下文窗口管理。
核心代码结构大致是这样:
def generate_long_text(topic, sections, reference_context):
"""
分段生成长文,每段保留前文摘要作为上下文锚点
"""
full_text = []
prev_summary = ""
for i, section in enumerate(sections):
# 构造当前段落的 prompt,带上前文摘要 + 真实参考资料
prompt = build_section_prompt(
topic=topic,
current_section=section,
prev_summary=prev_summary,
references=reference_context # 关键:外部资料注入
)
section_text = call_llm(prompt)
full_text.append(section_text)
# 生成本段摘要,作为下一段的上下文
prev_summary = summarize(section_text, max_words=200)
return "\n\n".join(full_text)
这个结构里有几个关键设计点:
设计点 1:先有大纲,再有内容
不要让 LLM 自己决定"接下来写什么"。把整篇文章的章节结构(sections)显式定义出来,让 LLM 严格按章节生成。
我后来发现,大纲这一步其实可以让 LLM 自己先生成一版,然后人工 review 调整,再用调整后的大纲去驱动正文生成。这样既利用了 LLM 的发散能力,又保证了人工控制。
设计点 2:滚动摘要作为上下文锚点
每生成一段后,把这段内容压缩成 ~200 字的摘要,作为下一段生成的输入。这样既绕开了上下文窗口的限制,又保持了前后逻辑的连贯性。
def summarize(text, max_words=200):
prompt = f"""请用不超过 {max_words} 字概括以下内容,
重点保留:核心观点、关键术语、已经使用的论据。
原文:{text}
"""
return call_llm(prompt, max_tokens=300)
设计点 3:参考资料显式注入(对抗幻觉)
要解决幻觉问题,最有效的方法不是 prompt 调优,而是给模型一个真实的资料库。
我的做法是预先准备好相关资料(论文摘要、行业数据、权威报告片段),存在向量数据库里。生成每段内容时,先根据 section 主题做检索,把相关资料作为 context 注入到 prompt:
def build_section_prompt(topic, current_section, prev_summary, references):
# 从向量库检索本段相关资料
relevant_refs = vector_db.search(
query=current_section,
top_k=5
)
refs_text = "\n".join([f"- {r.content}" for r in relevant_refs])
return f"""
主题:{topic}
本段标题:{current_section}
前文摘要:{prev_summary}
可用的参考资料(只能基于这些资料写,不要编造):
{refs_text}
请基于以上资料撰写本段内容,约 800-1200 字。
如果资料不足以支持某个论点,请明确说"资料不足",
不要编造数据或来源。
"""
最后这句 prompt 很关键 —— "资料不足请明确说出来,不要编造"。这一句话能让幻觉率显著下降,亲测有效。
三、生成后的处理:同样需要工程化
很多人以为生成完了就结束了。实际项目里,后处理环节的工作量不亚于生成本身。
我建议至少做这三件事:
1. 一致性检查
把生成的长文整体过一遍,让另一个 LLM 实例做"挑刺",主要检查:
def consistency_check(full_text):
prompt = f"""请审阅以下长文,检查并列出:
1. 前后定义不一致的术语
2. 数据/事实自相矛盾的地方
3. 论证逻辑跳跃或断裂的段落
用 JSON 格式输出,每条问题包括位置(段落号)、问题类型、问题描述。
原文:{full_text}
"""
return call_llm(prompt, response_format="json")
这个步骤能发现至少一半的低级错误。
2. 风格统一
分段生成的副作用是:每一段可能有微妙的风格差异(用词、句式、语气)。最后过一遍统一的 polish prompt:
polish_prompt = """请重写以下段落,保持原意不变,但:
- 统一专业术语用法
- 调整为正式书面语
- 拆解过长的复合句
"""
3. AI 痕迹检测与改写(这一点最近做项目时绕不过去)
现在很多接收方(学术机构、客户)会用 AI 检测工具反向扫描内容。如果你的输出需要"看起来不像 AI 写的",单靠生成参数调整是不够的,需要做语义层面的改写。
这里有个比较有意思的现象:AI 生成的文本有相对稳定的"统计指纹" —— 高频连接词、固定句式偏好、过度规整的段落长度。要降低这种指纹,比较有效的做法是:
def humanize(text):
prompt = f"""请改写以下文本,保持核心意思不变,但:
- 打破固定句式,有意混用长短句
- 去掉"综上所述"、"不可否认"等套话
- 加入一些口语化但不失专业的过渡
- 控制段落长度,不要全部一样长
原文:{text}
"""
return call_llm(prompt, temperature=0.9) # 注意:temperature 要拉高
注意最后一行——这里 temperature 必须高一点,否则模型还是会输出概率最高的"标准 AI 句式",等于白改。
四、绕不开的话题:这类需求适合自己写还是用现成工具?
写到这里,可能有同学会问:我业务里就一个长文本生成需求,要自己搭这套基建吗?
我的建议是分情况:
- 如果是给自己/小团队用,建议自己写。一是可以深度理解 LLM 的能力边界,二是后续要扩展功能(比如加 RAG、加格式化输出)也方便。
- 如果是高频、强合规的业务(比如学术写作、法律文书、医疗报告),市面上已经有不少专门的垂直工具,集成了上述的分段生成、资料检索、AI 痕迹处理等模块。直接用现成工具比自己搭一套更划算。
我自己写代码的同时,也调研过几款做长文本生成的垂直产品,发现它们底层基本都是上面这套思路的工程化封装。所以如果你不想踩坑,直接用现成的也是一种选择。这部分我就不展开了,免得变成软文。
五、一些零散的踩坑笔记
最后把我踩过的几个坑列一下:
1. 不要用 system 角色塞太多上下文
我一开始把所有的参考资料、风格指南都塞到 system prompt 里。后来发现,当 system prompt 超过 1000 token 后,模型对其中后半段内容的遵循度会显著下降。最好是把关键约束放在 user prompt 的开头和结尾(首尾效应)。
2. 不同模型的"性格"差异比想象中大
同样的 prompt,GPT-4 倾向于结构化、规整;Claude 倾向于细腻、有"温度";DeepSeek 偏向"理性、直接"。生成长文时,最好选一个模型从头跑到尾,混着用会出现明显的风格断层。
3. 流式输出和非流式输出的差异
流式输出(stream=True)在用户体验上更好,但有个坑:流式输出时模型的"自我修正"能力会弱一些,因为它无法"回头改写"已经输出的内容。对质量要求高的场景,建议用非流式 + 完整重生成。
4. token 计算别用估算
写代码时不要用"中文字符 × 1.5 ≈ token"这种估算公式,不同 tokenizer 的差异很大。建议用模型官方提供的 tokenizer 库精确计算,特别是接近上下文上限的时候。
import tiktoken
encoding = tiktoken.encoding_for_model("gpt-4")
token_count = len(encoding.encode(text))
写在最后
LLM 现在很强,但远没到"一句 prompt 解决所有问题"的程度。用好它,本质上是个工程问题,而不是 prompt 问题。
把长文本生成拆成可控的小步骤、显式注入真实资料、生成后做严格的质检——这些都是软件工程里最朴素的道理,套到 LLM 应用上一样成立。
希望对在做类似业务的同学有点参考价值。有不同实践经验的也欢迎在评论区交流。
更多推荐

所有评论(0)