做AI原生开发绕不开Prompt调优,我在实际项目里把Few-shot、CoT、ToT这三个技巧反复用了很多轮,踩了不少坑才总结出自己的一套用法。这篇就以一个商品评论结构化抽取的实战为例,完整拆解这三种高级Prompt技巧的原理、适用边界和组合姿势,适合正在用大模型做应用开发、又对输出稳定性和准确率不满意的朋友。
先交代一下背景:我接到的任务是从电商评论里抽取商品问题点,同时判断用户的退货倾向。一开始我写了个一次性Prompt,指令写得明明白白“只输出JSON”,结果模型时不时给我来一段废话,或者把退货倾向判断得特别武断。后来我把Few-shot、CoT、ToT逐个加上去,才把问题彻底治住。这中间的过程,比任何教程都更能说明问题。
1. 为什么普通Prompt不够用:三大技巧的底层逻辑
1.1 Few-shot:给模型“打样”比“讲道理”更有效
Few-shot的核心是在Prompt里放几个输入输出对的示例,让模型照着样子干活。最初我理解得很浅,以为就是“给个例子让模型模仿”,后来才发现它远比模仿更底层。
大模型训练时见过海量“前文接下文”的文本,你给它的每一段内容都会影响它的输出分布。示例的作用是重塑这个分布:你扔进去几个“评论→JSON”的配对,模型就会倾向于把当前这条评论也映射成类似结构的JSON。这就是为什么示例比指令更管用——指令是抽象的规则,示例是具体的分布锚点。
这里有个很容易被忽略的细节:示例本身同时承担了两个任务。一是显性的,告诉模型“我要什么格式”;二是隐性的,告诉模型“这个问题该用什么标准判断”。比如你给了两条“用户抱怨电池不耐用→退货倾向高”的示例,模型就会把“电池”问题和“退货倾向高”关联起来。所以示例不是随便找几条凑数的,示例的质量直接决定了模型行为的天花板。
工作里我常用3到5条示例,太少覆盖不了边界,太多会引入噪声。一个原则是:示例之间要有足够的差异,尤其要包含“反面情况”。比如你要判断退货倾向,不能全是“高”的示例,至少要有两条“低”的和一条“中”的。这个细节我后面在坑点里细说。
1.2 CoT:把“想”的过程写进Prompt
CoT(Chain of Thought)的思路是:不直接让模型给答案,而是让模型先把推理步骤写出来,再基于推理结果作答。最早看到那种“Let's think step by step”的零样本CoT时,我还有点不屑,觉得是玄学。真到自己跑实验才发现,加上这句话,复杂推理任务的准确率就是能涨一大截。
为什么有效?因为大模型的推理和人类做口算题很像:两位数的乘法你直接给答案,脑子容易打结,但拿张纸列个竖式,一步一步来,错的概率就低很多。CoT就是那张竖式纸——它把一次高难度的隐形推理,拆成几次低难度的显性推理,每一步的中间结果都暴露在外面。模型在生成“思考过程”时,每一步只需要做很小的决策,出错的概率被压低了,而且一旦出错,你还能从中间步骤里找到问题根源。
还有一个附加好处:CoT的中间推理给了开发者“审计”的抓手。以前模型直接输出结论,你只能瞎猜它为什么这么判断。有了推理链,你可以要求它引用原文,甚至可以只抽取那些“有证据支撑”的结论。这对于做内容审核、金融风控这类对可解释性有要求的场景,价值极大。
1.3 ToT:从“一条路走到黑”到“多路径搜索”
ToT(Tree of Thoughts)比CoT更进一步。CoT是一条推理链走到底,错了就错了。ToT则在推理的每一步都生成多个候选分支,对每个分支做评估、剪枝、回溯,相当于在思维空间里做搜索。
需要先说清楚一个容易误导人的点:ToT不像CoT那句话一样可以在Prompt里直接触发,它本质上是个算法骨架,需要你在代码里写循环:让模型在某个节点生成多个候选方案→让模型(或另一个评估器)打分→挑出最靠谱的继续往下走。你可能会在不同分支里来回试,直到走到一个可靠的终点。
我打个比方。CoT是走迷宫时选了一条路就一路走到黑,撞墙了才知道错了。ToT是走到岔路口时,先派几个分身各探一段路,拿回来对照一下再决定走哪条。代价肯定是多跑很多冤枉路,但复杂任务里的最终效果往往更稳。
把ToT做进生产环境时,我通常用它来跑“高风险决策样本”。普通样本用一次推理就够了,越是模棱两可的样本,越值得多开几条路径做对比。这跟人做判断的逻辑是一样的:小事情直接拍板,大事情多找几个人商量。
2. 技巧选型:什么场景用哪个,别一锅端
2.1 用错场景反而掉精度
刚开始用这些技巧时,我的心态是“能加就全加”,觉得技巧越多效果越好。实测下来完全是另一回事,每个技巧都有它不适合的场景。
Few-shot在分类任务里有个隐蔽的坑:示例的分布会直接污染输出分布。如果你给了3条退货倾向高的示例、1条退货倾向低的示例,模型就会倾向于预测“高”,因为训练时见过的条件分布里“高”的概率更大。哪怕真实数据里退货倾向低的评论占多数,模型也会被你的示例带偏。这件事我自查了很久才发现,排查过程后面单独讲。
CoT也有翻车的时候。在简单的事实问答或者模板化抽取任务里,让模型先展示推理过程,纯属浪费token,而且还有副作用:模型为了凑一个“看起来合理”的推理链,可能会凭空编造中间依据。你让它分析评论里的语气,它可能给你编一句评论里根本没出现过的描述,然后一本正经地基于它下结论。在需要严格忠实原文的场景里,这条尤其危险。
ToT的问题在于成本和延迟。每多开一条分支,就是多一次完整的大模型调用。线上接口如果平均响应时间要控制在2秒以内,ToT的分支一多就直接超时。再加上费用,一个分支走到底都费钱,更别提多分支并行。所以我的原则是:只在离线分析、复杂规划、或者是线上风险样本兜底时用ToT,普通请求绝不滥用。
2.2 一张表理清选择思路
我把三个技巧放在一张表里做过对比,也贴在下面供你参考:
| 技巧 | 核心思想 | 最适用场景 | 成本特征 | 实现难度 |
|---|---|---|---|---|
| Few-shot | 用示例锚定输出分布 | 输出格式要求严格、分类标准明确 | 只需增加示例token | 低 |
| CoT | 显性化推理步骤 | 多条件判断、数学推理、逻辑分析 | 推理段token开销 | 低 |
| ToT | 多分支搜索与评估 | 复杂规划、多种方案择优、开放问题 | 多次调用,成倍开销 | 高 |
选型的时候,我一般按三步走。第一步判断任务是不是“一次推理就能出结果”:如果只是格式化抽取,直接Few-shot就够;如果涉及多因素综合判断,那就加CoT。第二步看输出有没有“唯一正确答案”:有的话优先保证格式和稳定,没有的话可以考虑用ToT生成多条候选再筛选。第三步算算账:延迟和费用能不能承受多路调用,能承受才上ToT,不能就老实点用CoT。
这个决策流程看着简单,但我实际用下来,能省掉80%的盲目尝试。我见过很多人一上来就堆技巧,结果Prompt越搞越长,效果不升反降。记住一个原则:先确认问题本质上需要什么,再选择对应的技巧,而不是手里有锤子看什么都像钉子。
3. 实战改造:从普通Prompt到组合技巧完整过程
3.1 原始需求与初版Prompt:一次惨痛的翻车
需求背景很简单:我有5000条商品评论,需要抽取出“商品问题点”和“退货倾向”两个字段,后续要拿去做售后分析。当时偷懒,随手写了一个Prompt:
请分析以下商品评论,输出JSON,包含字段:issue(商品问题点)、return_intention(退货倾向,值为高/中/低)。 评论:{评论内容}还加了一句“只输出JSON,不要多余内容”。听起来没问题对吧?但实际跑下来,5000条里大概有25%的输出是不合格JSON,有时候是字段名换成了“问题”、有时候是“退货倾向”写成了“退货意愿”,还有时候直接输出一大段分析文字后才附上JSON。更烦的是退货倾向这个判断完全随机,同一个问题描述,换个说法判断就变了。
现在回看,问题很清楚:指令给的是“规则”,但模型需要的是“参考坐标”。规则只能约束方向,坐标才能约束落点。我当时最缺的就是Few-shot里那几个“坐标”。
3.2 第一步加Few-shot:先把格式焊死
第一条改造是加示例。我基于评论数据挑了几条典型样本,覆盖了“高、中、低”三个退货倾向档位,同时特意加入了一条“夸赞很多但也有一个小问题”的边界评论。Prompt变成这样:
请分析以下商品评论,输出JSON,包含字段:issue(商品问题点)、return_intention(退货倾向,值为高/中/低)。 示例1: 评论:用了三天就黑屏,申请售后一直没人理,再也不买这家了。 输出:{"issue": "屏幕黑屏、售后响应慢", "return_intention": "高"} 示例2: 评论:手机整体还行,就是电池掉电快,其他没毛病。 输出:{"issue": "电池续航差", "return_intention": "中"} 示例3: 评论:包装很好,发货快,用了一周没有任何问题,挺满意的。 输出:{"issue": "无", "return_intention": "低"} 评论:{评论内容}加了这三个示例之后,格式不合格率从25%降到了3%左右,说明示例的“格式锚点”作用立竿见影。但退货倾向的准确性还是不行,尤其遇到“既夸又骂”的评论,模型经常抓不住重点,给个“中”完事,但人工看下来这明显是“高”。问题出在:模型没有经历推理过程,直接跳到了结论。
这个阶段给我的教训是:Few-shot负责“模型做得像不像”,但它不负责“模型想得对不对”。要想判断准确,得让模型把“怎么得出结论的”这一步露出来。
3.3 第二步加CoT:逼着模型按评分卡走
第二步改造,我在JSON输出之前加了一个“分析过程”字段,让模型先给出推理,再输出结果。结构大概是这样:
请分析以下评论,推理要求: 1. 先列出评论中提到的问题点,引用原文关键词; 2. 针对每个问题点,评估严重程度(轻微/中等/严重); 3. 如果评论中有明确退货、不再购买、投诉等字眼,退货倾向直接判高; 4. 最后输出JSON,包含字段:issue、analysis(推理摘要)、return_intention。这里的重点是“拆步骤”。我没有笼统地说“请一步步思考”,而是给了一个带硬性规则的推理路径。比如“有明确退货字眼就判高”,这等于在模型脑子里埋了一个决策节点,比让它自由发挥要稳得多。
跑完之后,退货倾向的准确率明显上来了。拿人工抽样的100条数据做对比,判断准确率从65%左右升到了82%左右。最主要的变化是一些“语气强烈但问题不严重”的评论,以前会被误判成“高”,现在模型会先识别出“没有明确退货意愿”,然后给出“中”。
CoT也不是没有代价。最直接的感受是token消耗涨了约40%,因为每条评论都要先生成一段分析文字。另外,有些模型在分析过程里会写出一些评论里根本不存在的内容,比如“用户提到客服态度差”,但评论原文压根没提客服,这是模型依据上下文脑补出来的。后面我在Prompt里加了一句“推理只能基于评论原文内容,不得补充评论中未出现的信息”,情况才有所改善。
3.4 第三步用ToT思路:多方案对比避免一锤定音
CoT改造完之后,整体已经能用了,但我发现一个顽固问题:遇到模棱两可的评论,模型一次推理就敢下结论,而且每次结论可能还不一样。比如一条“东西不错但客服敷衍”的评论,第一次判“中”,第二次判“低”,波动很大。
这时候我把ToT的思路抽出来,但没做完整的树搜索,而是用了一个轻量级的变体。核心逻辑是:对低置信度样本,同时生成多个候选推理结果,然后汇总投票。实现起来也不复杂:
def analyze_with_consistency(comment, client, n=3): prompt = build_prompt(comment) # 复用带CoT的Prompt results = [] for _ in range(n): resp = client.chat.completions.create( model="your-model", messages=[{"role": "user", "content": prompt}], temperature=0.7, # 温度调高一点,让候选之间有差异 ) results.append(parse_json(resp.choices[0].message.content)) # 简单投票:取return_intention出现最多的值 intention_votes = Counter(r["return_intention"] for r in results) final_intention = intention_votes.most_common(1)[0][0] final_issue = merge_issues([r["issue"] for r in results]) return final_issue, final_intention这里我用的是类似Self-Consistency的思路:同一个Prompt采样多次,结果投票。它和完整ToT的区别在于没有中间步骤的显式分支和评估,但已经具备了“多条路径对比”的精髓。我实际只对“置信度边界样本”跑这种多方案投票,大概占全部数据的20%,其余80%样本走一次推理就够了。整体准确率又被拉高了5个百分点左右,而且输出稳定了很多,不再忽高忽低。
如果你要做完整的ToT,那还需要在推理中间步骤上做分支。比如让模型先产出3个“问题点清单”,对每个清单打一个合理性分数,再挑分数最高的清单接着推理退货倾向。那个工程量更大,但应对真正复杂的开放问题时,效果会更强。
这套组合Prompt的完整模板,我保存成了固定格式,放进了自己的工程模板库。核心结构是:任务描述 + 评分卡推理规则 + Few-shot示例 + JSON输出要求,外加一个“如果对结果不自信就多路采样”的后处理逻辑。后面所有类似的抽取任务,我都直接复用这个模板,效果都很稳。
4. 落地时最容易踩的坑和排查方法
4.1 Few-shot示例被模型“抄答案”
这是我最先踩到的一个坑,花了很久才发现。现象是:某一次跑分类任务,所有评论的退货倾向都被判定成了“高”。我以为模型抽风了,把温度调低、把指令加重,全都没用。
后来我把Prompt里示例的排列顺序打乱,发现结果跟着变了。才反应过来,问题出在示例本身——我放了3条“高”的示例,1条“低”的示例,模型在进行模式匹配时,发现样本里的“高”出现频率最高,就倾向于输出“高”,它把我给的示例当成了正确答案的分布参考。
解决方式有三个,我都验证过。一是示例里每个类别的数量尽量均衡;二是示例顺序要随机打乱而不是同类排一起;三是在示例后面加一句“注意:示例仅用于说明格式,请根据当前评论内容独立判断”。第三句看着简单,但对模型的行为纠偏非常有效,原理是明确降低了示例标签对输出分布的影响权重。
4.2 CoT推理链被模型“脑补”污染
CoT最大的副作用就是中间推理会被脑补内容污染。我做过一次分析任务,模型在推理过程中写了一段“用户对配送速度表示强烈不满”,但评论原文只有“产品很好”四个字,配送什么都没提。模型自己给自己加戏,然后基于加戏的内容下了个错误结论。
我的排查方法是:把推理过程和最终结论分开验证。先看推理中引用的信息是否都能在原文找到对应关键词,再对照结论是否与推理逻辑一致。后来我用了一个“可溯源”约束:强制模型在推理部分的每个判断后面,用括号标记原文关键词。比如“用户提到电池掉电快(原文:电池一天两充)”,这是一个特别实用的技巧,它把CoT从一个“无法验证的自由发挥”变成了“每个结论都有溯源的推理链”。
如果模型还是不老实,还有一个更狠但有效的办法:不用CoT的“边写边想”,改成两阶段Prompt。第一阶段只抽取原文证据,不判断;第二阶段基于证据做判断。证据和判断彻底分离,模型就没什么脑补空间了。
4.3 ToT多路验证的成本失控
ToT类方案最大的坑是成本失控。我第一次跑全量数据的多路投票时,账单直接翻了三倍,平均每条的响应时间也到了4秒以上,这个方案差点被当场否决。
我后来的做法是给多路验证加“准入条件”。先用单次CoT推理跑出一个置信度分数,比如“退货倾向为高且证据强度达到0.8以上”就直接出结果。只有分数落在边界区间(比如证据强度和结论有冲突、或多条问题互相矛盾)的样本,才进入多路验证流程。这么一整,多路验证只覆盖了总样本的20%,成本增量被控制在50%以内,而准确率的提升得以保留。
再补一句:如果用了多个分支做投票,记得给中间结果加缓存。同一批评论如果要做回归测试,模型输出可以缓存下来,不用每次都重新调用,省下来的时间和钱都非常可观。
4.4 把Prompt当代码一样做版本管理
最后分享一个习惯,这个习惯帮我在多次改造中没有把效果搞倒退。Prompt不是写一次就完事的,它和代码一样会持续演进。我在项目里给每个Prompt都建了版本号,用“评论分析Prompt v3.1”这样的命名方式管理。
每次修改Prompt,我不只凭感觉判断“好不好”,而是固定用一组回归测试集来验证。这个测试集是从历史数据里抽出来的100条覆盖各种场景的评论,包含边界案例、长评论、乱码评论等。每次改动之后,跑一遍回归集,对比“格式合格率”“判断准确率”“推理可溯源率”这三个指标。只要是这三个指标没有一项下降,改动才算通过。
这个习惯的回报非常直接:有一次我把CoT的Prompt改得更简洁了,肉眼看着效果不错,但回归测试一跑,发现“格式合格率”掉了一点。我一查,原来是简化后的推理步骤少了对原文的引用约束。如果没有回归测试兜底,这种细小的质量下滑很容易被忽略,等积累多了再排查,比当时发现要痛苦得多。
回归测试还有一个发明式的用法:它会慢慢变成一个“小型的验收文档”。团队协作或者交给别人维护时,不用从头读需求,跑一遍测试集就知道Prompt现在的行为边界。这比我写十页文档都有用。
这个组合技巧改造做完之后,我现在接手类似的结构化抽取任务,第一反应已经不再是“写个Prompt试试”,而是先问自己三个问题:这是格式问题还是判断问题?判断需不需要可解释?这个场景能承受多少次模型调用?想清楚这三点,再决定上Few-shot、CoT还是ToT。这个过程比套用任何现成模板都可靠,你按这个思路去试,大概率也能少走我走过的这些弯路。