生成式AI设计模式:RAG、结构化输出与反思循环的工程实践

发布时间:2026/11/4 8:14:07
生成式AI设计模式:RAG、结构化输出与反思循环的工程实践

两年前我第一次把大模型接入业务系统时,整个人是懵的。同一个问题,同一段Prompt,上午问和下午问,答案居然不一样。作为一个被"同样的输入必有同样的输出"训练了十多年的程序员,这种体验就像吃饭吃到一半桌子被抽走了。更崩溃的是,它偶尔还会在答案里一本正经地编造一个不存在的API函数名,下游代码一跑,编译器立刻教我做人的道理。

这不是某个模型的问题,这是生成式AI的底色。它本质是一个概率模型,按概率分布一个字一个字往外蹦。既然"随机"是它的天性,那做工程时就永远不能假设它稳定。可产品不能不稳定。所以这一年多,我在做AI应用时慢慢沉淀出一套应对办法,我把它们叫做生成式AI设计模式——针对生成式AI反复出现的问题场景,总结出的可复用解决方案模板。

这篇文章是系列的第一篇。我不会一上来就抛二十个模式,而是先讲清楚为什么需要一套方法论来兜底,然后给出我认为最基础、也最值得先掌握的三个模式:上下文增强检索、结构化输出加后置校验、候选-反思-择优循环。如果你正在用大模型API搭应用,或者准备往AI应用方向发展,这篇文章能帮你少踩不少坑。

1. 先搞清楚:生成式AI应用为什么这么难调

1.1 传统开发的"确定性"假设失效了

做传统软件时,函数只要输入一致,输出必然一致。单元测试天天跑,回归测试反复执行,靠的就是确定性。但生成式AI把这个假设打碎了。

以文本生成为例,模型本质是在根据前文预测下一个Token的概率分布。哪怕把参数完全固定,只要采样时引入一点随机性,结果就会在概率上漂移。这给工程带来四个直接麻烦:

  • 输出不稳定:同一Prompt下,语义可能接近,但具体措辞、结构、细节都不一样。
  • 格式不可控:让它返回JSON,它可能给你JSON夹带一段道歉;让它返回枚举值,它可能给你一个从未出现过的单词。
  • 幻觉难防:回答得越流利,编造事实的风险越高,尤其在专业领域。
  • 成本随次数线性长:一次生成不理想,就要重来一次,Token费用实打实地烧掉。

这四个麻烦,单靠调Prompt只能缓解,解决不了根本问题。就像你不能靠反复跟实习生说"别出错"来保证交付质量,你需要的是流程和检查机制。

1.2 生成式AI里的"设计模式"是什么

软件工程里的设计模式,是指在特定上下文下反复出现的解决方案。生成式AI设计模式同理,它不要求你发明新算法,而是把已被验证有效的做法沉淀成模板:什么场景下用、怎么搭、有什么坑、成本如何。

这套分类我在实际工作里重点看五个维度:如何控制输入(给模型什么信息)、如何控制输出(限制模型怎么回答)、如何补偿不确定性(出错了怎么修)、如何控制成本(能不能少调几次)、如何做工程治理(鉴权、限流、观测)。后续系列会陆续展开,这篇先聚焦前三类。

顺便说一句,这套模式和传统的GoF设计模式不是一回事。GoF关注的是类与对象之间的协作关系,面向的是确定性的程序结构;生成式AI设计模式关注的是模型输入输出之间的概率性协作流程,面向的是不确定性的闭环控制。换到Java、C++、C#里,思维方式照样能用,只是你编排的组件从对象变成了模型调用。

2. 模式一:上下文增强检索,让模型"开卷考试"

2.1 它解决什么问题

大模型的知识来自训练数据,因此有三个天然缺陷:

  • 知识有截止日期,新消息一概不知道。
  • 专业领域知识薄弱,企业内部文档、私有数据完全没训练过。
  • 就算见过类似资料,也可能记错、混淆,造成幻觉。

上下文增强检索(Retrieval-Augmented Generation,简称RAG)就是针对这三个缺陷的经典解法。核心思想一句话:既然模型"闭卷考试"容易胡编,那就把资料摆在它面前,让它"开卷考试"。你不需要重新训练模型,只需要在提问时,把相关的资料片段作为上下文喂给它,让它基于资料作答。

这个模式价值巨大:不需要微调模型,不需要把私有数据交给厂商训练,就能让模型"学会"一个领域的知识。企业知识库问答、文档摘要、合同审查这类场景,基本都是这个模式打底。

2.2 落地流程和关键参数

一个最小的RAG链路分四步:

第一步,把知识文档切成小块(Chunk)。

切块是个容易被小看的工程问题。块太小,语义被切断,检索不到完整上下文;块太大,噪声多,浪费Token还可能让模型忽略关键信息。我自己的经验是:中文通用文档从512到1024字符起步,再根据问答效果微调。如果文档有明显的段落标题、章节结构,优先按章节切,而不是机械截断。

第二步,把每块文本向量化,存进向量数据库。

选Embedding模型时要注意和检索场景的匹配,别拿对话模型顺手当向量模型用。目前主流向量库基本都支持常见的Embedding模型,关键是向量维度、相似度算法这些参数要和你的检索场景匹配。

第三步,把用户问题也向量化,做相似度检索,拿出最相关的若干块。

这里有两个参数常被忽略:

  • TopK:取几块上下文。取少了信息不足,取多了模型会"淹没"在信息里。建议从3到5块开始试试。
  • 相似度阈值:低于阈值的片段宁可不要。否则把不相关的内容硬塞给模型,反而诱导它胡说。

第四步,把检索结果和用户问题一起组装成带上下文的Prompt,交给大模型生成答案。

Prompt里要明确说清楚:"请只根据以下资料回答;如果资料中没有,请明确说不知道。"没有这句话,模型很容易又开始自由发挥。

2.3 一个容易忽略的坑

RAG不是加了就一定变好。检索质量差的时候,效果可能比不用RAG还差。因为模型会优先相信你喂给它的材料,如果喂进去的是无关片段,它也会一本正经地解释。所以数据清洗、切分质量、检索阈值这些前置环节,重要性往往比Prompt本身更关键。这是一个"输入决定输出"的模式——垃圾进,垃圾出。

我见过不少团队,上来就调Prompt,折腾一周效果还是不行,最后发现是向量库里塞了一堆格式混乱、没去重的旧文档。先把数据治理好,再谈模型能力。

3. 模式二:结构化输出与后置校验,让模型"按规矩说话"

3.1 为什么自由文本在工程里是灾难

生成式AI默认返回人类读着舒服的自由文本。好看是好看了,程序要消费却很麻烦。下游系统需要某个字段值去走审批流,结果模型返回"根据您的描述,我认为状态可能是A,不过也可能是B……",这套逻辑根本接不上。

工程上需要的不是漂亮话,而是可解析、可校验、可重试的数据结构。所以模式二的核心是两层:让模型尽量结构化输出,然后在后置环节强行校验,不合格就重试。

这就像让实习生写完工作汇报,部门助理负责检查格式、字段齐不齐、数值范围合不合理,不合格就打回去重填。汇报可以写得灵活,但交给领导之前必须符合固定模板。

3.2 输出约束的三种做法

第一种,纯靠提示词约束。在Prompt里写死"必须返回JSON,格式为……",再给一两个few-shot示例。零额外成本,但模型服从度不是百分之百,复杂Schema下经常出格式错误。

第二种,用模型的函数调用(Function Calling)或结构化输出能力。现在主流模型API基本都支持声明输出结构,模型会尽量按结构生成。这一步比纯提示词约束可靠得多,也省去很多解析麻烦。

第三种,在第二种基础上,再加一层后置校验和重试。这一步很多人会漏。模型毕竟是概率模型,哪怕99%服从,那1%的坏格式在生产环境就是事故。

3.3 校验器该做什么

一个实用的校验器至少做四件事:

  • 语法解析:确保是合法JSON,字段名称对得上。
  • 字段完整性:必填字段一个不能少,出现未知字段要决定是忽略还是报错。
  • 类型和取值范围检查:"status只能是open/closed/reviewing之一",模型就算输出了"Open",也要归一化之后才放行。
  • 业务规则校验:这个字段不能为空,那个金额必须大于零。

校验失败后,不要把错误抛给用户。直接把错误信息连同上一次的生成结果一起反馈给模型,让它修正重试。我在实践里发现,带明确错误信息的重试,成功率比单纯"重新生成"高很多,通常一两次就能拿到合规输出。当然要设最大重试次数,防止死循环烧钱。

下面是最小骨架,用Python风格伪代码示意,换Java、C++、C#都是一样的思路:

def generate_structured(prompt, schema): for attempt in range(3): raw = model.call(prompt, output_schema=schema) result, error = validate(raw, schema) if error is None: return result prompt += f"\n上次输出违反规则:{error},请据此修正。" raise MaxRetryError("结构化输出重试耗尽")

3.4 实操参数建议

把温度(temperature)调低一点,比如0.2甚至0,会显著提升格式稳定性。代价是答案变"保守",少一些惊喜,但对大多数业务场景来说,稳定大于惊喜。

另外,Schema不要设计得太复杂。能拍平就不要嵌套,嵌套最多两层。模型对深嵌套结构的生成能力会明显跳水,这一点我踩过不止一次。实测下来,温度从0.7调到0.2,格式错误率能从大概15%降到2%以下,代价是回答的创造性也低了,但对业务系统来说这是划算的交换。

4. 模式三:候选-反思-择优循环,用多次生成换质量

4.1 单次生成的天花板

模型再强,单次生成也有天花板。复杂任务里,它可能漏掉一个关键约束,或者在推理中途悄悄跑偏。指望一遍Prompt搞定所有问题,不太现实。

这时候需要模式三:先让模型生成多个候选,再让(同一个或另一个)模型扮演批评者,指出候选里的问题,然后带着批评意见重新生成修订版,最后再做质量择优。

这个模式的哲学是"别把宝押在一次随机采样上,用多次采样和反馈逼近正确答案"。它其实借鉴了软件开发里的Code Review:你先写一版代码,同事审核提意见,你再改,再合入主干。生成式AI当然干不了这么复杂的活,但思路是通的。

4.2 两种常见实现路径

第一种,同模型自反思。先生成初稿,然后让同一个模型批评自己的初稿:"你刚才的回答有没有事实错误?有没有遗漏需求?"再让它带着批评重新作答。实现简单,不需要接第二个模型,但批评的独立性有限——它很可能觉得自己"写得还不错",改进有限。

第二种,多模型互相评审。用一个更强、通常也更贵的模型当裁判,对初稿打分并给修改建议。效果更好,尤其适合内容质量要求高的场景,但成本明显上升。我常用的组合是:生成阶段用性价比高的模型,评审修订阶段用更强的模型,把成本花在刀刃上。

无论哪种,务必设置最大迭代轮数和终止条件。我见过团队把反思循环写成while True,结果模型从第二轮开始反复道歉、越改越短,白白烧Token。对多数场景,一轮生成加一到两轮反思就够了,再往后收获和成本的比值会急剧下降。

4.3 什么时候不值得用

如果回答"今天天气怎么样",你直接调天气API就好,根本不用生成式AI。就算需要生成,像"把这段文字翻译成英文"这种确定性较高的任务,单次生成往往已经不错,加反思纯属浪费。

所以模式三不是越用越好。它的适用场景是:任务复杂、错误代价高、有明确可检查性的内容生成,比如合规摘要、客服话术、复杂代码审查意见。用之前先自问一句:这个任务错了,代价有多大?如果代价不大,单次生成就够了。

5. 综合示例:一个企业知识库问答助手,如何把三个模式串起来

5.1 场景设定与架构串接

为了让上面三个模式不止停留在概念里,我拿一个典型场景来组合演示:

企业内部知识库问答助手。用户问政策问题,系统必须回答准确,还得注明出处;如果知识库没有对应内容,必须老实说不知道,不许编。

流程可以这样走:

  1. 用户输入问题。
  2. 上下文增强检索:从向量库检索出TopK条相关文档片段,附上文档ID。
  3. 组装Prompt,要求模型输出结构化JSON:包含answer、sources数组、confidence字段。
  4. 结构校验:验证JSON合法、sources非空、confidence在0到1之间。
  5. 失败则带校验信息重试;成功则走候选反思模式。为了控制成本,只对低置信度或不满足业务规则的答案做反思。
  6. 最终返回答案,把sources展示给用户。

5.2 伪代码骨架

def qa_assistant(question): chunks = retrieve(question, top_k=5) # 模式一:检索增强 prompt = build_prompt(question, chunks) schema = AnswerSchema.alias("Answer") # 模式二:结构化输出 answer = generate_structured(prompt, schema) if answer is None: # 校验失败重试耗尽 return "抱歉,暂时无法给出可靠答案,请转人工。" if answer.confidence < 0.6: # 模式三:低置信度才反思 critique = critique_model.criticize(answer) answer = generate_structured( build_revised_prompt(question, chunks, answer, critique), schema ) return answer

这个组合很有意思:模式一保证模型有据可答,模式二保证程序能消费结果,模式三只在关键节点兜底。三个模式各管一段,不会互相打架。

5.3 这样组合的成本与取舍

成本上,RAG本身便宜,只需要一次向量检索;结构化输出和普通调用等价;主要增量来自反思模式的重试。所以我刻意设计成"低置信度才反思",而不是每次都反思。

延迟上,完整链路多了一两次额外模型调用,用户等待时间会从几百毫秒涨到两三秒。这在内部工具场景完全可以接受,但如果做高并发的对外接口,就要谨慎评估——可能需要把反思放到异步流程,或者只对高风险请求启用。

6. 写在"(一)"之后:这套体系还会继续长

6.1 后续系列想展开的方向

这篇作为系列第一篇,我刻意只收敛了三个最基础的模式。它们的共同点是:都围绕"调用单个模型"这件事做文章,还没深入工程侧。而生成式AI应用真正上线之后,还有更多场景会反复出现——

比如输入输出网关模式:所有模型调用统一走一个中间层,做鉴权、限流、日志、按需降级。缓存与复用模式:同样或相似的请求,没必要每次都重新生成,特定场景可以用语义相似度命中后直接复用结果。多路投票模式:让多个模型或多个策略分别生成结果,最后用表决策略收口,用三倍成本换更高准确率。还有Agent编排里的工具调用、任务拆解、记忆管理,每个都值得单独展开。

这些内容我会在后续的"(二)""(三)"里继续写。你不用等我,如果手上正在做AI应用,可以直接按这个框架自己先试。

6.2 我给刚上手的开发者的三条建议

第一,别急着一次把所有模式都套上去。先用最简单的直连调用跑通流程,把数据、Prompt、校验先做扎实,再按需叠加模式。模式是工具,业务效果才是目标。

第二,任何模式落地前,先在几条真实业务数据上跑一遍基线。把单次生成的效果、成本、延迟记下来,加上模式后对比同一批数据。你会发现很多想当然的优化,实际收益和成本完全不成正比。

第三,用数字说话,而不是用感觉。AI应用工程本质上还是工程,能量化错误率、重试率、置信度分布,才能说服自己也说服团队。我在实际项目里最大的收获,不是会了几个花哨的Prompt技巧,而是养成了一套"先测量、再优化"的习惯。

希望这篇能帮你少走一些弯路。下一篇我会重点写工程侧的模式,尤其是输入输出网关和缓存复用,这两块对成本控制最立竿见影。到时候见。