我第一次接触大模型接口时,脑子里冒出来的就是 The Lamp and the Genie 这个画面。你擦亮神灯,灯神出现,说:“主人,你的愿望是什么?”你只要说出来,它就能做到。大模型 API 被封装好之后,确实很像这盏灯:输入一句自然语言,输出一段看起来像答案的内容。但现实很快把我拉了回来——同一个模型,有人能用它稳定地产出项目文档、数据清洗脚本、测试用例,有人连让它输出一段符合格式要求的 JSON 都要反复重试。差别不在灯的材质,而在使用者递出去的那句愿望。
今天我们把这件事拆开看:灯是接口,精灵是模型,你擦灯时说的话,就是提示词(Prompt)。很多时候,问题不是模型不够强,而是我们还在用“许愿”的方式和接口交流。
1. 同一个模型,为什么有人用得像神灯,有人用得像摸奖
1.1 模型本身不是工程,请求结构才是
先把一个很容易被忽略的区分说清楚:模型是一种能力,应用是一次成功调用。能力是静态参数,调用是动态过程。同样是开车,同一个引擎,有人开得平稳,有人频繁起步熄火。模型也一样。你向大模型发一个请求,本质上不是“问它一个问题”,而是“让它在你的约束下执行一个任务”。
这个任务能不能完成,取决于你如何描述任务边界、输入资料、输出格式和验收标准。很多朋友会拿模型直接聊业务问题,比如“帮我分析一下这个销售数据”,然后把一长段文本直接贴进去。模型确实会响应,但响应质量很容易飘。原因很简单:模型不是数据库,也不了解你的默认背景。它只能根据你提供的上下文、指令风格和概率分布生成下一个 token。如果你的指令没有把“分析”定义清楚,它就只能按训练语料里最常见的模式写一段泛泛而谈的总结。
这个差别,平时可能不明显。一旦换到严肃场景,比如让模型批量生成结构化字段、抽取实体、改写文案,同一个模型,输出质量可能一个天上一个地下。根源不在于模型版本,而在于你有没有把任务边界封住。
1.2 一句话塞太多需求,是大多数问题的起点
我见过很多刚接触大模型的开发者,第一版 prompt 通常长这样:
“请帮我根据下面这个项目写一份详细的技术方案,包括架构设计、数据库表结构、接口说明、部署步骤,还要注意安全性和性能优化,最好给出代码示例。”
这句话从人类角度看非常正常,但从模型的角度看,它是一个多目标任务,而且目标之间没有优先级,输出长度没有约束,验收标准也不明确。模型面对这种请求时,大概率会拆解成一个“看起来合理的平均结果”:每个部分都写一点,但每个部分都不深。
更麻烦的是,你想让它先做方案、再写接口,它可能把顺序打乱;你想让它给代码,它可能给的是伪代码;你想让它注意安全性,它可能只在最后加一句“需要加强安全”。这类问题,不是模型不够聪明,而是指令没有把“任务分解”和“交付物格式”交代清楚。
1.3 你可以把 Prompt 理解成“使用说明书”
跟大模型协作,和跟一个能力很强但完全不了解你业务的新同事协作很像。新同事背景知识扎实,但如果你只说“把这事处理一下”,他大概率会按自己的想法来。你需要交代背景、目标、输入、输出、约束、示例和验收标准。
Prompt 就是这份使用说明书。它不是文字游戏,也不是玄学,而是你把需求翻译成模型执行语言的过程。所以,“The Lamp and the Genie”这个隐喻的真正含义,不是“模型是魔法”,而是“魔法产生于你对灯的语言的理解”。模型确实强大,但精灵不会读心,它只会回应愿望的表面语义。
2. 灯的结构:拆解一次大模型请求的四个关键要素
2.1 System 消息:给精灵设定身份和边界
在实际的大模型接口中,一个请求往往不是只有一句“question”。常见结构会包含 system、messages 等字段。System 消息是用来设定整体行为边界的地方,相当于你在召唤精灵前先念规则:“你是一个资深数据分析师,只能使用中文回答,只能基于我提供的输入数据,不编造事实。”
很多初学者会忽略 system 的力量。他们把所有指令全塞到用户消息里,导致指令和输入数据混在一起,模型更难分辨哪些是任务、哪些是材料。更合理的方式是:
- System 里写角色和规则,比如“你是一个严谨的软件架构师”“不要使用 Markdown”“不确定时输出 null”。
- User 里写给模型的具体任务和输入材料。
- Assistant 消息可以放历史回答或 few-shot 示例。
这样分层的意义在于,让模型在“进入任务前”先完成人设和约束的设定,而不是在具体请求里临时切换。
2.2 User 消息:把愿望说清楚,而不是写一段小作文
User 消息是真正的人类请求。这里最需要克制。有人会觉得 prompt 越长越代表“有丰富上下文”,于是把一大段背景、多个任务、输出要求、禁止事项都堆在一起。实际上,模型的注意力是有限资源,信息密度比长度更重要。
一个有效的 User 消息通常包含四块:
- 任务:我要你做什么。
- 输入:给你什么材料。
- 约束:有哪些限制,比如不能编造、必须引用原文。
- 输出格式:返回什么结构,例如 JSON、Markdown、表格。
如果有多个子任务,可以拆成编号列表,让模型按顺序处理。如果你希望它只做一件事,就别在 prompt 里同时给它三件事。否则它会在三者之间找平衡,而不是完成你真正需要的那一个。
2.3 输出格式:先在 Prompt 里定好,不要事后解析
实战中最大的坑之一,是让模型“自由发挥”之后再希望它输出 JSON。你可能遇到过:模型返回了 Markdown 代码块,里面套着 JSON,但代码块前后有说明文字;或者 JSON 键名不符合预期;或者字符串内的引号被转义错。
关键是把输出格式变成约束,而不是期待。下面是一个常见写法的简化示例:
prompt = """ 请从下面的商品评论中抽取以下字段: - sentiment: 正面 / 负面 / 中性 - category: 商品类型 - summary: 不超过15个字的总结 只输出 JSON,不要包含 Markdown 代码块。 不要输出其他文字。 评论: {comment} """这里最关键的是最后三行约束:“只输出 JSON”“不要包含 Markdown 代码块”“不要输出其他文字”。这比在代码里写“请把 JSON 解析出来”要可靠得多。但就算这样,上线前仍然要写解析兜底逻辑。因为模型是概率输出,不是强约束解析器。
2.4 参数:温度、长度和随机性不是越大越好
接口里的 temperature、max_tokens、top_p 等参数,也是灯的开关。很多人不理解这些参数意味着什么,随手设成 0.9 或 1.0,然后抱怨输出反复无常。经验上:
| 参数 | 作用 | 建议取值 |
|---|---|---|
| temperature | 控制随机性,越低越稳定 | 抽取、分类用 0.1~0.3;创意写作用 0.7~0.9 |
| max_tokens | 限制输出长度 | 要比正常需要的长度多留一点,否则结果会被截断 |
| top_p | 核采样概率,控制候选词范围 | 一般不用和 temperature 同时大调 |
需要记住的是:
- 对抽取、分类、结构化输出,尽量用低 temperature,让输出更稳定。
- 对头脑风暴、文案创意,可以适当调高,但要知道这会牺牲一致性。
- 如果同时调整 top_p 和 temperature,可能会互相干扰。通常建议先固定一个,再调另一个。
注意:不要一上来就把 temperature 调到 0.9,然后抱怨输出不稳定。先明确这个任务到底是偏稳定,还是偏创意。
3. 从模糊愿望到可执行指令:提示词设计的五步法
3.1 第一步:先写任务骨架,而不是一段连贯文案
当我们要设计一个真正可复用的 prompt 时,不要一上来就写一整段话。先把任务拆成骨架:
- 角色:谁来做。
- 背景:为什么做。
- 任务:具体做什么。
- 输入:提供什么。
- 输出:格式和长度。
- 约束:不能做什么。
- 验收:怎么判断合格。
这个骨架看起来很基础,但很多人并不真的执行。他们的 prompt 是“帮我写一个 Python 函数,把 CSV 文件读取后做数据清洗”,却没有告诉模型字段缺失怎么办、日期格式是什么、输出是否需要保留原始列、异常数据是否要记录。结果模型给出一个理想化的、能跑的代码,但一接到真实数据就崩,因为它不知道缺失值策略,所以风险全留在后面。
3.2 第二步:把模糊词替换成可测试条件
“详细”“准确”“合理”这类词,人类能懂,但模型无法把它变成验收条件。你需要把:
- “准确”换成“只能基于给定文本,不能自行补充信息”。
- “详细”换成“每个步骤不少于 3 个操作说明,并列出涉及的命令”。
- “合理”换成“输出结果满足以下三个条件:……”。
可测试条件意味着你可以写一段脚本去校验输出。比如,检查输出是否包含指定字段,JSON 是否能被解析,长度是否超过阈值。如果一个约束没法被校验,模型大概率不会认真对待。
3.3 第三步:加入 Few-shot 示例,但别让示例喧宾夺主
Few-shot 是指在 prompt 中给出一两组输入输出对,让模型照着样式做。这比单纯描述格式更直观,特别适合分类、抽取、风格改写。示例的价值在于,它是一种约束的具象化。
但这里也有一个常见误判:示例越长越细,效果就一定越好?不一定。如果示例和真实输入相差太远,模型会模仿示例的表面格式,而不是理解规则。示例应该覆盖边界情况,而不是重复普通情况。
比如一个情感分类任务,与其给三个“正面”示例,不如给一个正面、一个负面、一个中性,再给一个带有营销话术的“假正面”样本。这样模型才知道边界在哪里。
3.4 第四步:先跑通一条样本,再讨论规模化
设计好 prompt 之后,别立刻批量调用。先用 5 到 10 条有代表性的样本做小规模验证。这既是为了看输出质量,也是为了看成本、延迟和稳定性。
我在实践中的做法是:
- 准备一个 test.csv,包含不同场景的输入。
- 写一个脚本循环调用,把 prompt 和输出都记录到日志里。
- 人工或写规则检查前 20 条输出。
- 遇到不满足条件的,先改 prompt,不要调参数。
- 当 20 条都通过之后,再扩大到 100 条、1000 条。
这个流程看起来慢,但它能避免你批量产出大量无用结果之后才发现问题。成本上,查一次 prompt 的代价,远低于清洗一批坏数据。
3.5 第五步:把每一次失败变成下次迭代的输入
Prompt 不是一次写成的,它需要版本管理。哪怕只是加了一个示例,也可能改变输出分布的倾向。所以,最好的习惯是每次修改都记录:
- 输入是什么。
- 期望是什么。
- 实际输出是什么。
- 为什么失败。
- 改了什么。
- 效果如何。
一段时间后,你会形成一份属于自己的 prompt 经验库。这里要特别提醒:不要一遇到输出不满意就立刻往 prompt 里加限制词。过多“不要”“禁止”会引入冲突,反而让模型混淆。更稳妥的做法是,把希望它做什么写清楚,而不是把所有不希望发生的都列一遍。
4. 当精灵开始胡说:幻觉、不可重复和边界控制
4.1 模型不是数据库,也不是搜索引擎
即使是能力很强的大模型,也会出现“一本正经地胡说八道”。这不是它态度不好,而是生成机制决定的:模型在做 token 概率预测,而不是查询事实。如果一个问题在训练语料里不常见,或者上下文里的信息不足以支撑推理,模型就会用最像样的方式补全。
这种补全有时候是对的,有时候是错的,但语气往往同样自信。理解这个机制很重要,它意味着两件事:
- 不要把事实类问题完全交给模型。
- 让模型回答事实类问题时,必须给它可靠的知识来源,比如资料片段、数据库结果或工具返回。
4.2 不可重复:Temperature 低也不代表每次输出完全一样
你可能会发现同一个 prompt 多次调用,结果有细微差别。这是因为模型采样过程本身带有随机性。temperature=0 也只是尽量取最高概率,但某些实现里仍可能受随机种子影响。所以,如果你的应用需要可重复输出,比如自动化测试或审核,不能幻想 prompt 一致就结果一致。
工程上更推荐的做法是:
- 对输出做幂等处理,比如只取第一个 JSON 对象。
- 对关键场景做重试策略,比如解析失败后换 temperature 重新生成一次。
- 在业务上允许同一个输入偶尔出现表达不同、含义相同的输出。如果业务不能接受,需要加校验和归一化逻辑。
4.3 边界控制:什么不该交给模型决定
使用大模型前,先画一条线:哪些环节模型可以参与,哪些环节不能。
可以:文本改写、抽取结构化字段、生成初稿、代码补全、总结、分类。 不应该:对健康、法律、金融等高风险场景做最终判断,不经过人工复核直接执行关键操作。
这里的边界不是否定模型能力,而是承认概率模型的不确定性。你可以在 prompt 里加“如果你不确定,请回答不确定”,但模型并不总是可靠地遵守。真正的边界控制要靠代码逻辑:凡是不能接受错误结果的输出,都要有人工确认或规则校验。
4.4 输出异常时的排查链路
如果遇到模型输出质量下降或报错,建议按这个顺序排查:
- 先看现象:是报错、空输出、格式不对、内容错误,还是速度慢。
- 再看输入:字符串是否被截断,编码是否正确,字段是否拼接错,上下文是否缺失。
- 再看提示词:角色是否清楚,任务是否单一,输出格式有没有约束,示例有没有歧义。
- 再看参数:temperature 是否过高,max_tokens 是否限制得太小,是否用了不兼容的参数。
- 最后看工具边界:版本是否太老,API 是否限流,当前模型是否适合这个任务,是不是任务本身超出了模型能力。
这个链路不要跳步,也不要一上来就怀疑“模型太差了”。多数问题其实出在第 2 步和第 3 步。
如果校验失败了,先别急着重试。看失败类型:是 JSON 格式错误,还是字段值不符合规则。不同的失败原因,处理方式完全不同。
5. 从一次召唤到流水线:把 Prompt 变成可维护的工程模块
5.1 用模板管理 Prompt,而不是散落在代码里
Prompt 一旦进入业务,就不要直接写在调用函数里。它应该像配置一样被管理。常见做法是维护一个模板文件,用变量占位符填充输入。比如:
你是一个客服工单分类助手。 请根据下面的工单内容判断问题类型,并输出 JSON。 工单内容:{{ticket_content}} 输出格式:{"category": "硬件/软件/账户/其他", "confidence": 0-1}然后在代码里读取模板,把变量替换成实际数据。这样做的好处是,产品和运营同学可以独立调整 prompt,不需要动代码。甚至可以在后台配置不同版本的 prompt,做 A/B 对比。
5.2 输出校验和重试:大模型接口不是纯函数
我之前说过,模型是概率输出,所以任何依赖模型输出的流程,都要有一个输出校验层。这个校验层至少要做三件事:
- 格式校验:能否解析成 JSON、字段是否存在、枚举值是否合法。
- 内容校验:关键字段是否为空,是否出现“我不知道”之类的结果。
- 业务校验:是否符合某个业务规则,比如金额不能为负数,日期不能早于创建时间。
校验失败后,可以重试一次,但不要无限循环。重试时可以改成更保守的参数,或者提示模型“你刚才的输出格式不符合要求,请重新输出”。加一轮对话式的修正,往往比直接改 prompt 更有效。
下面是一个简化伪代码流程:
def call_with_validation(prompt, validator, max_retry=2): for i in range(max_retry + 1): output = call_model(prompt) if validator(output): return output raise ValueError("模型输出多次校验失败")5.3 成本和延迟:批量任务别用一个循环硬扛
当任务量从几条变成几万条时,单纯写一个 for 循环会踩很多坑:接口限流、超时、并发冲突、成本失控。工程上需要分批处理、设置并发数上限、加超时和退避重试,并且估算 token 消耗。
token 消耗不仅和输出长度有关,也和 prompt 长度有关。如果你每次请求都附带大段背景资料,这些 token 会计费,也会增加延迟。所以 prompt 不是越长越好,要按性价比取舍。可以把不必要的历史记录、重复描述去掉,只保留模型完成任务必需的信息。
5.4 评估集和回归:让 Prompt 像代码一样可以被测试
Prompt 是软件的一部分,所以它应该有测试。你可以维护一个评估集:包含 50 到 100 条典型输入,以及每条输入对应的“期望行为”。这里的期望行为不一定是完整答案,可以是一组检查规则。
每次修改 prompt 后,跑一遍评估集,看通过率是否下降。这样你的优化才不是拍脑袋。这可能是最容易被忽略的一环。很多人会花很多时间调 prompt,却没有任何客观标准。结果就是:改了一个示例,某个 case 变好了,另一些 case 变差了,自己根本不知道。
有了评估集,你至少能看到回归风险,也能在版本升级或更换模型时,快速判断新模型是否适合当前场景。
6. 回到灯与精灵:真正的壁垒不是魔法,而是理解接口的人
“The Lamp and the Genie”这个隐喻到这里可以收束了。灯里的精灵确实强大,但强大的能力只有在清晰、可验证的召唤方式下,才会变成可靠的生产力。模型本身是灯,API 是灯口,而 prompt、参数、数据流和校验逻辑,是你擦灯的手指和念出的愿望。你愿意投入多少去理解这个接口,决定了你能从模型身上“召唤”出多少稳定的价值。
6.1 把模型当协作者,而不是许愿机
我现在越来越觉得,Prompt Engineering 不是短期技巧,而是人与大模型协作的基本功。传统接口调用,输入输出规范是明确的,偏差是可预见的。大模型则是“宽进严出”:你给它模糊的输入,它就还你模糊的输出;你给它清晰的约束和足够的上下文,它才能接近你的预期。
所以,一个成熟的开发者会在写 prompt 之前先问自己三个问题:
- 我到底想让模型完成一个什么任务?
- 这个任务的成功标准是什么?
- 如果模型输出不符合标准,我的兜底方案是什么?
这三个问题想清楚,哪怕 prompt 写得不华丽,效果也不会太差。
6.2 下一步:先从一个最小可验证任务开始
如果你现在正准备用大模型做一个小工具,我的建议很简单:不要急着写一个很长很全的 prompt。先找一个最小的任务,把输入、输出、约束和示例写清楚,跑通 10 条样本,再逐步加复杂度。
等你把这条链路跑稳了,再回头理解“灯与精灵”的隐喻。那时你会意识到,真正的魔法不在模型里,而在你把需求翻译成接口约束的过程里。模型很可能还会继续变强,但一个能清晰定义任务、设计约束、验证输出的人,永远不会被工具替代。