外观
第 10 章 复杂逻辑的 Prompt 表达
10.1 提示词即程序
这一章的内容,可能会挑战工程师过去几年甚至十几年建立起来的、对“编程”这件事的传统认知,甚至带来一丝本能的不安和困惑。
这一章要回答的,就是这个问题:怎么用提示词表达工程师熟悉的逻辑控制。先讲“提示词即程序”这个新范式,说清它凭什么成立、强在哪、边界在哪;然后依次给出条件分支、循环与递归在 Prompt 里的写法,再讲介于大白话和代码之间的伪代码提示,最后补上异常处理的兜底设计,每一种都配了可以直接改用的模板。读完这一章,再面对一堆复杂的业务规则,就能判断哪部分逻辑写进 Prompt 更划算、怎么写才不容易出错。
工程师一提到“逻辑控制”,脑子里立刻会浮现出什么?是 if-else 的二元抉择,是 for 循环的精准迭代,是 switch-case 的清晰分流,是烂熟于心的那些精确、严谨、不容置疑的关键字。习惯了用一种形式化的、无歧义的编程语言(比如 Python、Java、C++)来告诉计算机“在什么情况下,做什么事”。这种确定性,这种对机器的绝对掌控感,是工程师面对多变业务时安全感的来源。
但是,如果在一段看似普通的,甚至有点啰嗦的“大白话”里,就能实现同样复杂的逻辑控制呢?
来看一个能瞬间带来“范式冲击”的场景。假设一位工程师正在为一个大型电商平台开发一个智能客服机器人,其中一个核心功能,就是处理用户的退款请求。产品经理发来一张密密麻麻的需求表,里面的业务规则非常复杂。“服装”或“鞋靴”类商品,只要在购买后 7 天以内,就无条件退款;“家用电器”或“数码产品”要分两种情况,包装未拆封、购买后 7 天以内也可以退款,但包装已拆封,就不能直接退款,需要引导用户去申请售后检测,判断是否存在质量问题;“生鲜食品”或“虚拟商品”(比如游戏点卡),则一概不支持退款;至于所有超过 7 天的订单,都礼貌地拒绝退款,并告知原因。
用熟悉的传统编程语言,该怎么实现这个逻辑?大概会先把规则理清,然后用一个由 if-elif-else 构成的多层嵌套结构,把每个分支写出来:
python
def handle_refund_request(product_category, days_since_purchase, is_opened):
if product_category in ["服装", "鞋靴"] and days_since_purchase <= 7:
return "好的,根据我们的‘七天无理由退货’政策,已为您办理退款。"
elif product_category in ["家用电器", "数码产品"]:
if not is_opened and days_since_purchase <= 7:
return "好的,未拆封的电子产品支持七天无理由退货,已为您办理退款。"
elif is_opened:
return "根据政策,电子产品一旦拆封,非质量问题无法退货。如果您怀疑有质量问题,可以为您引导至售后检测流程。"
else: # 超过7天
return "抱歉,您的商品已超过 7 天退货期限。"
elif product_category in ["生鲜食品", "虚拟商品"]:
return "抱歉,根据平台规定,生鲜及虚拟商品不支持退换货。"
else:
if days_since_purchase > 7:
return "抱歉,您的订单已超过 7 天无理由退货期限。"
else:
# 这是一个兜底逻辑,处理其他所有品类
return "抱歉,您购买的商品类别不支持无理由退款,详情请查阅平台政策。"看着这段代码,是不是感觉特别亲切?逻辑清晰,边界分明,确定性百分之百。这是工程师世界的“通用语言”,是信赖的基石。但是,它有一个致命的问题,一个在快速变化的业务面前显得尤为突出的问题。它是“硬”的,是“脆”的。
这种逻辑结构坚固,但毫无弹性。如果明天,产品经理跑过来说:“好消息!我们跟供应商死磕下来了,‘图书’品类现在支持拆封后 24 小时内无理由退货!赶紧上线!” 怎么办?只能打开 IDE,修改原来的代码,在某个 elif 后面再加一个新的分支,仔细地写单元测试,确保没有影响到其他逻辑,然后走一遍完整的提交、审查、构建、部署的 CI/CD 流程,还要承担这次上线引入新问题的风险。
现在换一种方式。把这些业务逻辑,直接用“人话”,用一种结构化的方式,写进 Prompt 里:
markdown
# 角色
你是一位资深、严谨且富有同理心的电商客服专家,你的核心任务是根据平台政策,准确地处理用户的退款请求。
# 核心指令
你必须严格遵循以下业务规则逻辑树,来决定如何回复用户的退款请求。在分析时,你需要像一个精密的决策引擎一样,逐层判断,不能有任何遗漏和自由发挥。
## 业务规则逻辑树 (Business Rules Logic Tree)
### 第一层判断:商品品类
- **情况 A: 如果商品属于【服装、鞋靴】类别:**
- 进入**第二层判断:购买天数**
- **如果** 购买天数小于或等于 7 天,**则** 你的标准回复是:“好的,根据我们的‘七天无理由退货’政策,已为您办理退款。退款将在1-3个工作日内原路返回。”
- **否则** (购买天数大于7天),**则** 你的标准回复是:“抱歉,服装鞋靴类商品仅支持七天无理由退货,您的订单已超过期限,无法办理退款。”
- **情况 B: 如果商品属于【家用电器、数码产品】类别:**
- 进入**第二层判断:包装状态**
- **如果** 商品包装**未拆封**:
- 进入**第三层判断:购买天数**
- **如果** 购买天数小于或等于 7 天,**则** 回复:“好的,未拆封的电子产品支持七天无理由退货,已为您办理退款。”
- **否则** (购买天数大于7天),**则** 回复:“抱歉,您的商品已超过 7 天退货期限。”
- **如果** 商品包装**已拆封**:
- **则** 你的标准回复是:“根据平台政策,电子产品一旦拆封,非质量问题将无法办理退货。如果您怀疑产品存在质量问题,我可以为您引导至售后检测流程,由专业工程师为您鉴定。”
- **情况 C: 如果商品属于【生鲜食品、虚拟商品】类别:**
- **则** 你的标准回复是:“抱歉,根据平台规定,生鲜及虚拟商品因其特殊性,一经售出不支持任何形式的退换货。”
- **情况 D: 对于所有其他未明确提及的品类:**
- 进入**第二层判断:购买天数**
- **如果** 购买天数大于 7 天,**则** 回复:“抱歉,您的订单已超过平台统一的 7 天无理由退货期限。”
- **否则** (购买天数小于等于7天),**则** 回复:“抱歉,您购买的商品类别暂不支持无理由退款,具体政策请您查阅商品详情页的说明。”
---
### 用户当前请求信息
- **商品品类**: 数码产品
- **购买天数**: 3 天
- **包装状态**: 已拆封
### 生成回复
请根据以上的业务规则逻辑树和用户当前请求信息,生成最终的、完整的、可以直接发送给用户的客服回复。把这个长长的 Prompt 发给 GPT-5、Claude 4.5 或 DeepSeek 这类能力足够的旗舰模型,它会输出什么?
它会输出:“根据平台政策,电子产品一旦拆封,非质量问题将无法办理退货。如果您怀疑产品存在质量问题,我可以为您引导至售后检测流程,由专业工程师为您鉴定。”
它准确无误地执行了这段用自然语言描述的、层层嵌套的逻辑分支!
这个看似简单的例子,背后揭示了一种新的编程范式,提示词即程序(Prompt-as-Program)。
在这个范式下,不再仅仅使用形式化的编程语言来编写和执行逻辑,而是开始用结构化的自然语言来描述业务规则和执行流程。大语言模型(LLM)在这里扮演的角色,与其说是一个简单的、被动调用的“函数库”(比如一个文本生成函数),不如说是一个能够主动理解和执行这些自然语言程序的“虚拟机”或“解释器”。
读到这里,心里可能充满了疑问、不屑,甚至是一丝本能的抵触:“这不就是把 if-else 换了个花里胡哨的写法吗?看起来又啰嗦又不靠谱,性能肯定差得要死,这玩意儿能上生产吗?它的不确定性我怎么控制?我怎么给它写单元测试?”
别急,先放下这些本能的抗拒。它的作用,以及它所适用的场景,远比第一印象要广。
“提示词即程序”范式的优势,最直观的是灵活性。再回到刚才那个让人头疼的场景。现在,产品经理又跑过来说:“好消息!我们跟供应商死磕下来了,‘图书’品类现在支持拆封后 24 小时内无理由退货!赶紧上线!” 在传统模式下,又得开始改代码、提 PR、等测试、排期上线……一套流程下来,最快也得半天,这还是在一切顺利的情况下。
但在“提示词即程序”的模式下,需要做什么?不需要改一行 Python 代码,不需要重新构建和部署服务。只需要打开 Prompt 管理后台(或者一个简单的配置文件),在那个“业务规则逻辑树”的文本里,增加一条新的“情况 E”: - **情况 E: 如果商品属于【图书】类别:** - 进入**第二层判断:购买时长** - **如果** 购买时长小于或等于 24 小时,**则** 回复:“好的,图书商品支持24小时内拆封退货,已为您办理退款。” - **否则**,回复:“抱歉,图书商品仅支持24小时内拆封退货,您的订单已超时。”
然后点击“保存”或“发布”。系统的核心业务逻辑,随即实时更新了。
这种业务逻辑的迭代速度,是从“天/小时”级别,直接跃升到了“分钟/秒”级别。对于那些市场环境瞬息万变、业务规则需要频繁调整的场景(比如电商大促的活动规则、金融风控的策略模型、内容推荐的算法权重),这种敏捷性是决定性的。
比灵活更深的红利,是业务赋权。把那段层层嵌套的 Python 代码拿给公司的产品经理、运营专家或者法务看,他们大概率是一脸茫然,然后客气地说一句:“你很专业,我们相信你。” 但他们其实根本无法审查其中的逻辑细节,规则的正确性完全依赖于工程师“翻译”得是否准确。 但把那段用“如果-则”写的、结构清晰的自然语言规则给他们看,他们不仅能完全看懂,稍加指导甚至能自己上手修改!
这从根本上改变了业务流程的协作模式。业务规则的定义和维护,第一次有可能从研发团队的“技术黑盒”中解放出来,回归到最懂业务的业务团队自己手中。工程师的角色,从“业务逻辑的翻译和实现者”,转变成了“保障这套自然语言程序能够稳定、安全运行的平台架构师和维护者”。这是一种巨大的、跨部门的生产力解放,是真正的“BizDevOps”。
还有一重更隐蔽的优势,是处理模糊性。传统代码的 if 判断,必须是精确的、布尔的。product_category == "服装",多一个字、少一个字、大小写不对,都不行。 但如果用户的输入是“我上周给我儿子买的那件带奥特曼的衣服”,传统代码就无法处理了。得在前面再加一堆复杂的自然语言处理(NLP)逻辑,做实体识别、意图分类,才能把“带奥特曼的衣服”映射到“服装”这个品类上,这个过程本身就充满了不确定性。
而 LLM 解释器本身就擅长处理这种模糊性。在“自然语言程序”里,可以写下这样的规则: * **如果** 用户描述的商品听起来像任何一种可以穿在身上的衣物(例如:衣服、裤子、裙子、外套、T恤),**则** 将其归类为“服装”。 模型能够利用它强大的语义理解能力,去执行这种“模糊匹配”。这是传统编程范式需要耗费巨大成本才能勉强实现的。
当然,绝不能鼓吹“提示词即程序”可以完全取代传统编程。它也有明显的弱点,这些弱点正是工程师需要去解决的挑战,集中体现在四个方面。结果带有非确定性;可测试性较差;性能开销和成本更高;还存在潜在的安全风险。
但是,它提供了一种前所未有的新能力。对于那些业务规则极其复杂且频繁变化、逻辑中包含大量模糊判断、需要快速迭代和 A/B 测试的场景,用 Prompt 来“编程”,可能是一种比传统硬编码逻辑更优雅、更敏捷、也更符合业务本质的解决方案。
这一章的核心任务,就是学习如何成为一名合格的“自然语言程序员”,学习如何用最清晰、最没有歧义的“人话”,去教会模型精准地执行想要的 if-else、for 循环,甚至是 try-catch。
10.2 在 Prompt 里写下 If-Then-Else
条件控制(If-Then-Else)是所有编程语言的核心结构,是程序“智能”的起点,它赋予了程序根据不同情况做出不同“决策”的能力。在“提示词即程序”这个全新的范式中,同样需要一种可靠的、工程化的方式,来让模型根据不同的条件,执行不同的动作。
幸运的是,LLM 在它庞大的训练数据中,已经“阅读”并“理解”了人类社会中蕴含条件逻辑的无数文本,从各种家用电器的使用说明书(“如果红灯闪烁,则表示电量不足”),到公司的各项规章制度(“如果迟到超过30分钟,则记为旷工”),再到严谨的法律条文。因此,它对“如果……那么……否则……”这种结构,有着非常强的理解力。
这里的任务,不是去“发明”这种能力,而是通过一系列工程化的技巧,把这种本来就有、但有时不稳定的理解力,变得可靠、稳定、可预测、可维护。
方法一:用“如果-那么-否则”结构描述决策树
这是最简单,也是最常用的一种方法。相当于把业务规则写成一份详尽的《标准作业程序(SOP)手册》,把逻辑树用清晰的、层层嵌套的“如果-那么-否则”结构,完整地描述出来。
再来仔细剖析一下刚才那个退款的例子,看看它之所以能够成功运行,背后隐藏着哪些关键的设计原则:
markdown
# 业务规则 (Business Rules)
你必须严格遵循以下逻辑树来决定如何回复用户:
1. **首先,判断商品品类:**
* **如果** 商品是 **“服装”**:
* **然后,判断购买天数:**
* **如果** 购买天数小于等于 7 天,**则** 回复:“...”
* **否则**,回复:“...”
* **如果** 商品是 **“电子产品”**:
* ...
* **对于所有其他品类**,统一回复:“...”这个结构之所以有效,绝对不是巧合,它里面至少包含了四个关键的设计。
明确的执行指令与角色绑定,说的是“你必须严格遵循以下逻辑树……”这句话。它是在设定一个“解释器模式”。它告诉模型,接下来的内容不是一段普通的背景描述,而是一套需要它逐条执行的规则。将这个指令与“你是一位专业的电商客服”这个角色绑定,进一步强化了其执行任务的严肃性。
清晰的视觉层次结构,靠的是 Markdown 的缩进和列表,把嵌套的 if-else 逻辑,在视觉上清晰地呈现了出来。这不仅仅是为了方便人类工程师阅读和维护。对于模型来说,这种视觉上的层次感,能极大地帮助它理解不同条件之间的父子关系和执行优先级。它能识别出,关于“购买天数”的判断,隶属于“商品是‘服装’”这个分支下面。
逻辑关键词的高亮与实体标注,指的是用 Markdown 的加粗,把 如果、则、否则 这些逻辑流程的关键词,以及 “服装”、“电子产品” 这些判断条件中的核心实体,都高亮了出来。这相当于把关键信息显式标出来。模型在解析这段文本时,会优先关注这些高亮的关键词,从而快速地构建起整个逻辑的结构,而不至于淹没在具体的文本描述里。
完备的“兜底”逻辑(Default Case),对应的是“对于所有其他品类……”这一句,它就相当于代码里最后的那个 else 分支,或者 switch-case 里的 default 语句。它保证了无论遇到什么意想不到的输入,逻辑都是闭环的,都有一个明确的出口。这能极大地防止模型在遇到未知情况时,不知道该怎么办,从而开始“自由发挥”或“胡说八道”。在设计任何逻辑系统时,都值得专门想一想“当所有条件都不满足时该怎么办”。
什么时候应该优先使用这种方法?当业务逻辑可以清晰地表达为一棵决策树时,当逻辑分支相对有限(经验值是,总分支数最好不超过 10 到 15 个),并且可以明确地枚举出来时,这种直白的“如果-那么-否则”结构是首选。它最直观,最接近人类的思考方式,也最容易向非技术同事解释和交接。
方法二:决策表(Decision Table):处理多维度复杂逻辑
当条件判断变得更复杂,不再是一棵简单的树,而是涉及多个维度的“排列组合”时,层层嵌套的 if-else 很快就会变得难以阅读和维护。
设想一个电商平台的优惠券使用规则,可能同时取决于“用户等级”(VIP/普通/新用户)、“订单金额”(>500/>200/>100)、“商品品类”(图书/服装/电器)、“活动时间”(是否在双十一期间)这四个维度。用 if-else 去写,可能要写出 3 * 3 * 3 * 2 = 54 种组合的嵌套判断,写出来连作者本人过几天都很难看懂。
在这种情况下,可以借鉴传统软件工程领域里一个非常经典、但很少有人重视的工具,决策表(Decision Table)。它的核心思想,就是用一个二维的表格,来清晰地、无遗漏地描述所有可能的条件组合,以及每种组合对应的执行动作。
来设计一个稍微复杂一点的 Prompt,根据用户属性自动生成 App 首页的个性化问候语。
markdown
# 任务
你是一个 App 的个性化推荐引擎。请根据当前用户的实时属性,生成一句精准、友好的首页问候语。
# 决策表 (Decision Table)
你必须严格按照下表的规则矩阵,从上到下进行匹配,一旦找到第一个满足所有条件的行,就立即采用对应的问候语模板,并停止继续向下匹配。
| 规则优先级 | 用户等级 | 当天是否生日 | 最近24小时内是否有订单 | 问候语模板 |
| :--- | :--- | :--- | :--- | :--- |
| 1 | VIP | 是 | * | "尊贵的 VIP 会员 [用户名],生日快乐!感谢您一路相伴,我们为您准备了一份特别的生日惊喜,请在‘我的卡券’中查收!" |
| 2 | VIP | 否 | 是 | "尊贵的 VIP 会员 [用户名],下午好!您昨日的订单正在飞速打包中,预计今天就能发出哦!" |
| 3 | VIP | 否 | 否 | "尊贵的 VIP 会员 [用户名],下午好!好久不见,我们上新了一批您可能感兴趣的好物,快来看看吧!" |
| 4 | 普通会员 | 是 | * | "亲爱的 [用户名],生日快乐!祝您今天过得愉快,我们为您准备了一张生日专属优惠券!" |
| 5 | 普通会员 | 否 | 是 | "亲爱的 [用户名],下午好!感谢您的信任,您的订单我们已经收到啦!" |
| 6 | 普通会员 | 否 | 否 | "亲爱的 [用户名],下午好!欢迎常来看看,今天也有不少新品上架哦!" |
| 7 | 新用户 | * | * | "欢迎新朋友 [用户名]!很高兴认识你,送您一份新人专享大礼包,开启您的发现之旅吧!" |
*注:星号(*)表示“任意值”,即该条件不参与本条规则的判断。*
---
# 用户实时信息
- 用户名:阿强
- 用户等级:VIP
- 当天是否生日:否
- 最近24小时内是否有订单:是
# 执行
请根据以上决策表和用户实时信息,生成最终的问候语。模型看到这个表格会怎么做?它会一行一行地核对,开始进行“匹配”。先读第一行规则,用户等级是 VIP?是。当天是否生日?否,不满足“是”的条件,跳过。接着读第二行规则,用户等级是 VIP?是。当天是否生日?否,匹配。最近是否有订单?是,匹配。所有条件满足!于是执行动作,采用第二行的问候语模板,将 [用户名] 替换为“阿强”。再根据指令“一旦找到第一个满足……就立即停止”,停止匹配,不再继续往下看。最终输出的,正是那句“尊贵的 VIP 会员 阿强,下午好!您昨日的订单正在飞速打包中,预计今天就能发出哦!”
决策表的优势是巨大的。它是处理多维复杂逻辑最有效的形式,当条件维度达到 3 个或更多时,表格的清晰度远胜任何形式的 if-else 嵌套,它把一个网状的逻辑,压缩成了一张二维表格,一目了然。它还自带强制的完备性检查,通过表格可以很容易地检查,是否覆盖了所有可能的条件组合,如果表格里有“空洞”,就说明逻辑有漏洞。而对业务人员来说,这是他们最熟悉的形式,阅读和修改一个类似 Excel 的表格,远比理解一段嵌套的逻辑描述要容易得多,可以直接把这个 Prompt 里的表格部分截图发给产品经理,让他自己确认逻辑有没有问题。
方法三:Few-shot 示例,用“归纳法”教会模型难以规则化的逻辑
有时候,判断逻辑是模糊的、感性的,很难用几条非黑即白的规则来清晰地描述。它更多地依赖于“感觉”、“经验”或者对上下文的深刻理解。
比如,想让模型判断一封销售邮件的“潜在意向强度”,分为“强”、“中”、“弱”三档。 这个“强度”本身,就很难用规则说清楚。客户直接问“价格多少?”,可能是强意向;说“我考虑一下”,可能是中等意向;说“你们的产品很有趣”,可能是弱意向。但如果客户在问完价格后,又追问了“你们能提供定制化方案吗?”,那意向强度就又提升了。
在这种情况下,硬要去写“如果-那么”规则,不仅费力不讨好,而且很容易覆盖不全。更好的方法,是放弃“演绎法”(给规则,让模型推导结果),改用“归纳法”(给大量的“判例”,让模型自己总结规律),也就是已经非常熟悉的 Few-shot Prompting。
markdown
# 任务
你是一位顶级的销售专家,擅长从客户的邮件中嗅出商机。请分析以下销售询盘邮件,并判断客户的“潜在意向强度”。你只能回答“强”、“中”、“弱”中的一个。
# 判例分析 (Case Studies)
---
### 案例 1
**邮件内容:**
“你好,我在你们网站上看到了 XYZ 解决方案。我们公司最近正在进行数字化转型,对这个很感兴趣。能否提供一份详细的报价单和技术白皮书?另外,我们希望下周能安排一次技术交流会,我们 CTO 会参加。”
**意向强度:** 强
**分析笔记(仅供你学习):** 客户主动提及自身需求,要求报价和白皮书,并主动提出高层参与的会议,这些都是非常强烈的购买信号。
---
### 案例 2
**邮件内容:**
“你们的 XYZ 解决方案看起来不错,能简单介绍一下吗?”
**意向强度:** 弱
**分析笔记(仅供你学习):** 客户只是提出了一个非常宽泛的、信息收集性质的问题,没有表露任何具体的需求或急迫性,属于初步了解阶段。
---
### 案例 3
**邮件内容:**
“谢谢你们上次的介绍。我们内部讨论了一下,觉得方案在技术上是可行的。不过,关于价格,我们觉得有点超出预算了。不知道在合作模式上,有没有更灵活的选择?”
**意向强度:** 中
**分析笔记(仅供你学习):** 客户已经认可了技术方案,但卡在了价格上,进入了商务谈判阶段。有明确的阻碍点,但也有继续沟通的意愿,属于中等强度。
---
# 输入数据
请分析以下这封新邮件:
**邮件内容:**
“你好,我们正在评估市面上的几种解决方案,你们是其中之一。请问你们的系统能否和我们现有的 SAP 系统集成?如果可以,集成工作大概需要多久?”
**意向强度:**这个 Prompt 的设计精髓,在于那个 判例分析 部分。它提供了多样化的案例,给出了“强”、“中”、“弱”三种情况的典型案例。它还提供了“元认知”信息,每个案例后面,都加上了一个 分析笔记。这个笔记,相当于对判断依据的说明,它向模型解释了“我为什么会做出这样的判断”。这能极大地帮助模型学习到判断逻辑的“精髓”,而不仅仅是模仿表面的输入输出。
通过这几个精心挑选的、带有“专家批注”的案例,模型会自己去“悟”出判断意向强度的复杂标准。它会学到,要关注客户的“具体动作”(是索要资料还是提出会议)、“决策阶段”(是初步了解还是商务谈判)、“提出的问题类型”(是宽泛问题还是具体的技术问题)。
这种方法,特别适合处理那些依赖深层语义理解、而非简单规则匹配的条件判断任务,不需要费力地去总结规则,只需要把“判例法”知识,通过几个典型的“案例”和对应的“判断依据”,传授给模型就行了。
总结一下,如何选择条件控制的表达方式?这是一个实战中非常有价值的决策点。建议是这样的。如果逻辑是清晰的、确定性的、可以枚举的,那么优先使用 “如果-那么-否则”结构 或 决策表,它们更可靠,更接近“代码”。如果逻辑是模糊的、感性的、依赖经验和上下文的,那么 Few-shot 示例 的效果会好得多,它更灵活,更接近“人类的直觉”。
在实际的复杂项目中,甚至可以把这几种方法组合起来使用。比如,用一个大的 if-else 结构来做任务的初步分流,然后在某一个特定的、需要模糊判断的分支里,再调用一个专门的、基于 Few-shot 的 Prompt 来处理更精细的判断。这种混合编程的思路,是高级 Prompt 工程师的必备技能。
10.3 让模型处理一堆而不是一个
模型已经学会了如何做“选择题”(条件控制)。但现实世界中的编程任务,除了选择,还有大量的“重复劳动”,也就是循环。
比如,把一个 Excel 表格里的所有英文产品名,都翻译成中文;或者,检查一篇几千字文章里的每一个段落,看是否含有敏感词。
在传统编程里,这太简单了,一个 for 循环就搞定了。但在 Prompt 里,该怎么向一个只会“从左到右”生成文本的模型,表达“对……中的每一个,都做……”这种循环逻辑呢?
这是一个在 Prompt Engineering 里公认的、非常棘手的难题。因为 LLM 的核心机制是“一次性”的序列生成,它并没有一个内置的、类似 for 循环的状态记忆和迭代计数器。让它处理一个列表,它很可能只处理了前几个就遗漏后面的。
不过,依然有一些工程上的技巧,可以模拟出循环的效果,甚至在某些情况下,比传统循环更优雅。
方法一:指令映射(Instruction Mapping):一条指令覆盖全部元素
这是最简单,也是与 AI 对话时最符合直觉的方法。不需要教模型什么是 for 循环,只需要在指令里,用一种不容置疑的语气,明确地告诉它,对输入集合的每一个元素,执行同一个操作。
设想这样一个场景,一位产品经理拿到了一份工程师写的、充满了技术黑话的新功能列表,需要把它们翻译成用户能听懂的、有吸引力的“人话”,用于更新 App 的“新功能介绍”页面。
markdown
# 任务
你是一位顶级的产品文案专家(Product Marketing Manager),你的超能力是将复杂的技术术语,转化为普通用户一听就懂、且怦然心动的宣传语。
请将以下 JSON 数组中的每一个“功能描述”(description),都进行一次“翻译”和“润色”,改写成一句对普通用户更有吸引力的宣传语。
# 核心约束
- 你必须处理数组中的 **每一个** 对象,不能遗漏。
- 保持原始 JSON 数组的结构和 `feature_id` 字段不变。
- 你的工作是且只是修改 `description` 字段的值。
# 输入数据 (工程师黑话)
[
{
"feature_id": "F001",
"description": "我们实现了一个基于 QUIC 协议的、优化的低延迟数据传输通道。"
},
{
"feature_id": "F002",
"description": "所有用户数据现在都通过 AES-256 加密算法进行端到端加密,以符合 GDPR 标准。"
},
{
"feature_id": "F003",
"description": "我们重构了状态同步模块,现在支持基于 CRDT 的多设备状态实时同步。"
}
]
# 输出 (用户人话)模型在处理这个 Prompt 时,虽然它的底层并没有一个真正的 for 循环计数器,但它强大的模式匹配能力和指令遵循能力,会让它理解到,这个任务的“模式”是,接收一个包含多个对象的数组,然后对数组里的每一个对象的特定字段,执行一次“改写”操作,最后再把它们组装成一个新的数组。
它很可能会输出:
json
[
{
"feature_id": "F001",
"description": "体验如丝般顺滑!我们优化了底层传输技术,让你的图片和视频加载快如闪电。"
},
{
"feature_id": "F002",
"description": "你的隐私,我们用银行级的安全标准来守护。所有信息都经过端到端加密,坚不可摧。"
},
{
"feature_id": "F003",
"description": "无缝切换你的手机、电脑和平板。你的工作进度,现在可以在所有设备上实时同步,从不间断。"
}
]这种方法的优点是简单直接,对于处理长度较短(比如 10 个元素以内)的列表,效果通常不错。
但它的缺点也同样致命,头一个就是不稳定性。当列表变得很长(比如上百个元素),或者要执行的操作很复杂时,模型很容易输出不稳定的结果。它可能处理完前十个,就遗漏了后面的,或者处理到一半,输出的 JSON 格式就乱了。长列表缺少显式的进度记录,生成到后面就容易偏离要求。除了不稳定,还有上下文长度限制的问题,把整个列表都塞进一个 Prompt 里,会消耗大量的上下文 Token,如果列表非常大,可能会直接超出模型的上下文窗口限制。调试也困难,如果最终输出的 100 个结果里,有一个是错的,很难定位到是哪个具体的输入导致了问题。
所以,比较稳妥的定位是,指令映射法只适合用于快速原型验证,或者处理那些长度可控、重要性不高的非生产任务。
方法二:分治法(Divide and Conquer),结合外部代码的真循环
如果追求的是绝对的稳定性、可控性和可扩展性,尤其是在处理生产环境中的、可能非常长的列表时,更稳妥的选择是,不要把循环的逻辑交给模型。
应该把“控制权”牢牢地掌握在自己手里。也就是说,在外部代码(Python, JavaScript 等)里,自己写一个真正的 for 循环。在循环的每一次迭代里,只从列表里取出一个元素,然后调用一个只负责处理单个元素的、小而美的、高度优化的 Prompt。
来看一下这种模式的伪代码实现,这才是工业级的做法:
python
# 这是一个只负责处理单个元素的、高度优化的 Prompt 模板
# 它非常简单,职责单一,因此非常稳定
single_item_prompt_template = """
你是一位产品文案专家。请将以下这条技术功能描述,改写成一句对普通用户有吸引力的宣传语。
你的回答必须非常简洁,只输出改写后的那一句宣传语,不要包含任何多余的解释或问候。
技术功能描述:"{description}"
用户宣传语:
"""
# 这是你的原始数据列表
input_data = [
{"feature_id": "F001", "description": "基于 QUIC 协议..."},
{"feature_id": "F002", "description": "采用 AES-256 加密..."},
# ... 这里可能有成千上万个元素
]
# 这是你用来存放结果的列表
output_data = []
# 在我们自己的、可靠的外部代码中,实现真正的循环
for item in input_data:
try:
# 1. 为每个元素动态构建一个简单、独立的 Prompt
prompt = single_item_prompt_template.format(description=item["description"])
# 2. 调用 LLM API,只处理这一个元素
rewritten_description = call_llm_api(prompt)
# 3. 对结果进行清洗和校验(非常重要!)
# 比如去掉模型可能多生成的引号或换行符
cleaned_description = rewritten_description.strip.strip('"')
# 4. 组装最终的结果
output_data.append({
"feature_id": item["feature_id"],
"description": cleaned_description
})
except Exception as e:
# 5. 健壮的异常处理
print(f"处理 feature_id {item['feature_id']} 时发生错误: {e}")
# 你可以选择跳过、记录日志,或者用一个默认值填充
output_data.append({
"feature_id": item["feature_id"],
"description": "描述转换失败"
})
# 循环结束后,output_data 就是你最终的、可靠的结果集看到这段代码,可能会觉得:“这比直接扔一个大 Prompt 麻烦多了!”
是的,它确实更“麻烦”。但这种“麻烦”,是工程师为了换取系统的确定性和鲁棒性,必须付出的、值得的代价。
这种“外部代码循环 + 单点原子化 Prompt”的模式,是工程上处理批量任务的主流做法。
它的好处是明显的。稳定性与可预测性极高,每个 Prompt 的任务都极其简单(只处理一件事),模型几乎不可能出错或产生格式问题。它无视列表长度,列表即使有一万个元素,这个模式也能稳定地处理,因为它每次只“看”一个。可并行化还带来了超高性能,可以轻易地把这个 for 循环,改造成一个并行处理的模式(使用线程池或异步 IO),同时向 LLM API 发送几十上百个请求,处理一万个元素的速度,可能比用“指令映射法”处理一百个元素还要快。
调试与重试机制也足够精细,如果其中某一个元素的处理失败了(比如遇到了网络波动或 API 错误),只需要重试那一个就行了,而不会影响到其他已经成功处理完的 9999 个结果。
它的唯一缺点,就是 API 的总调用次数会变多,理论上总成本可能会略高于一次性处理。但是,考虑到现在 API 的价格越来越便宜,以及并行处理带来的巨大时间节省,为了换取整个系统的稳定性和可维护性,这点成本的增加,在绝大多数严肃的工程场景下,都是完全值得的。
方法三:模拟递归(Simulated Recursion),在模型的“思维”里处理嵌套结构
处理扁平的列表已经有办法了。但如果要处理的,不是一个简单的列表,而是一个具有无限嵌套可能性的数据结构呢?比如,一个文件目录树,或者一个复杂的 JSON 对象。
设想这样一个任务,需要为一个多语言的国际化(i18n)JSON 文件做处理,里面所有值为字符串的字段,都加上一个 _translated 的后缀,以便后续进行翻译。
json
{
"user": {
"name": "John Doe",
"bio": "A software engineer."
},
"settings": {
"theme": "dark",
"notifications": {
"email": "daily_summary"
}
}
}对于这种问题,可以设计一个能够“自我调用”的 Prompt,来模拟递归。
markdown
# 任务
你是一个 JSON 处理器。你的任务是递归地遍历一个 JSON 对象。
对于每一个键值对:
- 如果值是一个字符串,就在键的后面加上 "_translated" 后缀。
- 如果值是一个对象,就对这个子对象递归地执行相同的操作。
- 其他类型的值保持不变。
# 工作流
1. 接收一个 JSON 对象作为输入。
2. 创建一个新的空对象作为输出。
3. 遍历输入对象的每一个键(key)和值(value)。
4. 根据值的类型,执行以下操作:
* **如果** value 是字符串,则在新对象里设置 `key + "_translated": value`。
* **如果** value 是对象,则**递归地调用你自己**来处理这个子对象,并将返回结果设置到新对象里。
* **否则**,直接在新对象里设置 `key: value`。
5. 返回新对象。
# 示例
输入: {"title": "Hello"}
输出: {"title_translated": "Hello"}
# 输入数据
请处理以下 JSON 对象:
{
"user": {
"name": "John Doe",
"bio": "A software engineer."
},
"settings": {
"theme": "dark",
"notifications": {
"email": "daily_summary"
}
}
}这个 Prompt 的精髓,就在于 工作流 里的那句“递归地调用你自己”。这句话,是在引导模型去理解和模拟递归的思考过程。
虽然模型并没有真正地像程序一样执行函数调用,但它会在生成结果时,保持这种递归的“状态”,先处理外层,再处理内层,从而正确地完成任务。
当然,这种模拟递归对模型的要求非常高,而且很容易在嵌套层数很深的时候“栈溢出”(忘记自己在哪一层了)。在实际工程中,如果遇到复杂的树状或图状结构处理,更稳妥的方法,还是在外部代码里自己写递归或遍历算法,然后只让 Prompt 去处理最底层的那个“叶子节点”的任务。
10.4 伪代码是自然语言与代码的中间形式
前面讨论的所有方法,都是在用“纯自然语言”来描述逻辑。这对业务人员来说很友好,但对工程师来说,有时候会觉得不够直接,不够“硬核”。
而且,当逻辑变得极其复杂,包含了变量赋值、状态传递、多重循环嵌套时,纯自然语言的描述会变得非常冗长,也容易产生歧义。
这时候,就需要一种介于自然语言和形式化编程语言之间的“中间语言”。这个中间语言,就是每个程序员在大学里都学过的伪代码(Pseudocode)。
伪代码就是这样一种语言。它没有严格的语法,但有清晰的结构;它用自然语言来描述操作,又借鉴了编程语言的关键字和控制流。它适合作为工程师与 LLM 沟通的中间形式。
来看一个场景。假设要让模型实现一个稍微复杂一点的算法:找出一个数字数组中的第二大值。
用纯自然语言描述,可能会很绕: “请你遍历这个数组,你需要维护两个变量,一个叫最大值,一个叫第二大值。一开始它们都设为最小值。然后你一个个数地看,如果当前的数比最大值还大,你就把原来的最大值赋给第二大值,然后把当前的数更新为最大值。如果当前的数介于最大值和第二大值之间,你就只更新第二大值……”
这段描述冗长,而且容易产生歧义,模型处理起来也容易出错。
现在,用伪代码来重写这个指令:
markdown
# 任务
你是一位资深的 Python 程序员。请根据以下伪代码描述的算法,实现一个 Python 函数 `find_second_largest(nums)`。
# 算法描述 (伪代码)
function find_second_largest(nums):
if length of nums < 2:
return null
largest = -infinity
second_largest = -infinity
for each number in nums:
if number > largest:
second_largest = largest
largest = number
else if number > second_largest and number != largest:
second_largest = number
if second_largest == -infinity:
return null // 处理所有元素都相同的情况
else:
return second_largest把这个 Prompt 发给模型,它会怎么想? 它会觉得:“哦!太亲切了!这不就是我最熟悉的语言吗?”
它会立刻识别出 for each、if...else if... 这些清晰的逻辑结构,然后几乎可以 1:1 地把它翻译成真正的 Python 代码。
伪代码提示的优势是巨大的。论精确性,对于复杂的算法和流程,伪代码是消除自然语言歧义的可靠手段,它在保留了自然语言灵活性的同时,提供了编程语言的结构化和精确性。它也是工程师最熟悉的表达方式,对写代码的人来说,阅读和编写伪代码,比写大段的自然语言描述,要轻松和高效得多,它也让 Prompt 的审查(Review)变得更容易。它还有跨语言能力,可以用一套伪代码,让模型分别生成 Python、Java、JavaScript 等不同语言的实现,伪代码成了独立于具体语言的、更高层次的“逻辑规范”。
什么时候应该使用伪代码提示?当你需要模型实现一个具体的、逻辑复杂的算法时,当你需要定义一个包含多个步骤、且步骤之间有状态依赖的工作流时,或者当你发现用纯自然语言已经无法清晰地描述意图时,就应该用它了。
可以说,伪代码提示,是“提示词即程序”这个范式下的“专业版”。它可能对非技术人员不太友好,但对工程师来说,它是一种能精准、高效地控制模型复杂行为的手段。
10.5 教会模型兜底
一个鲁棒的程序,不仅要能处理正常情况,更要能优雅地处理各种异常情况。工程师的代码里总是充满了 try-catch、if err != nil 这样的防御性代码。
“自然语言程序”也一样。如果只考虑理想情况,那线上各种意想不到的输入一定会破坏它。
必须在 Prompt 里明确地教会模型:“当你遇到未知情况、当你无法完成任务时,你应该怎么办?” 这就是异常处理(Error Handling)。
来看一个场景。做了一个根据公司名称查询公司信息的工具。正常情况下,用户输入“腾讯”,模型就应该去调用 API,返回腾讯的信息。
但用户可能输入的,是一个不存在的公司,比如“宇宙第一有限公司”;也可能是一句模棱两可的话,“就是那个做聊天软件的公司”;甚至可能是一句攻击性的脏话。
如果 Prompt 里没有异常处理逻辑,那么对于不存在的公司,模型可能会“幻觉”出一个地址和 CEO 出来;对于模棱两可的话,它可能会瞎猜一个,或者直接报错;对于脏话,它可能会生成不恰当的回复。
这些都是不希望看到的。一个带有异常处理逻辑的 Prompt,应该长这样:
markdown
# 任务
根据用户提供的公司名称,查询并返回公司的基本信息。
# 工作流
1. 分析用户输入,提取出明确的公司名称。
2. 调用 `query_company_info(company_name)` API 来查询信息。
3. 根据 API 的返回结果,执行以下操作。
# 异常处理规则 (Error Handling Rules)
- **如果** 用户输入无法提取出明确的公司名称(例如,输入的是一句闲聊或一句不完整的话),**则** 你必须追问用户:“请提供一个明确的公司全称,以便我为您查询。”
- **如果** `query_company_info` API 返回了 `null` 或者 `Not Found`(表示公司不存在),**则** 你必须回复用户:“抱歉,我没有查询到您所说的公司信息,请检查公司名称是否正确。” **严禁** 在这种情况下编造任何信息。
- **如果** 用户输入包含不友好或攻击性的内容,**则** 你必须忽略其字面意思,并回复:“让我们专注于讨论有价值的问题吧。”
# 正常流程
- **如果** API 成功返回了公司信息,**则** 将信息格式化成一个简介,然后回复给用户。可以看到,这个 异常处理规则 模块,就是为这个“自然语言程序”编写的 try-catch 块。
它明确地定义了三种“异常”情况,以及每种情况下模型应该执行的“兜底”动作。
这带来的好处是巨大的。系统的鲁棒性得到了提升,它在正常路径之外,对异常输入也有了明确的行为。安全性也增强了,通过明确的规则,有效遏制了模型在遇到未知情况时产生“幻觉”或做出不当行为的风险。用户体验同样改善了,当用户输入错误时,他得到的不再是一个冷冰冰的错误信息,而是一个友好的、引导性的追问。
在设计任何一个生产级的 Prompt 时,请务必专门花点时间,按测试工程师的思路检查一遍:“用户可能会有哪些‘不按套路出牌’的输入?我的 API 可能会返回哪些异常状态?” 然后,把这些“异常”都作为明确的条件,写进 Prompt 里。
这份投入是值得的,这些规则写进 Prompt 之后,系统能自动处理大部分异常请求,不需要人工随时盯着。
本章总结 从对话者到程序员
这一章的内容,彻底打破了“Prompt 就是跟 AI 聊天”的简单认知,进入“提示词即程序”这个全新的领域。在这里,写 Prompt 的人不再只是 AI 的“使用者”,而是它的“程序员”,编程语言第一次变成了以自然语言为载体的东西。
这一章的方法体系也很清晰。条件控制上,最基础的 If-Then-Else 结构和更高级的决策表赋予模型“决策”的能力;面对“一堆”而不是“一个”的重复任务,指令映射、分治法、模拟递归三种手段各有各的适用范围;工程师专属的伪代码提示,是与模型沟通复杂算法的中间形式;最后用异常处理思想给“自然语言程序”加上 try-catch,教会模型按规则应对失败,系统才谈得上真正鲁棒、可靠。
学完这一章应该能深刻地体会到,Prompt Engineering 发展到高级阶段,其内在的思维模式,与传统的软件工程是殊途同归的。这里的工程师同样关心逻辑的严谨性、结构的可维护性、边界的清晰性和系统的鲁棒性。
只不过,“编译器”从 GCC 换成了 GPT-5,“编程语言”从 Python 换成了结构化的中文。
它要求用一种跨界的视角,去重新审视“代码”与“语言”、“逻辑”与“语义”之间的关系。
当然,用 Prompt 编程,目前还处于一个非常早期的阶段。它还有很多问题没有解决。但它所展现出的巨大潜力,包括极高的敏捷性、与业务的无缝结合、处理模糊性的能力,预示着它很可能会成为未来软件开发中,不可或缺的一环。
参考资料
- OpenAI (2025). Introducing GPT-5. 2025-08-07. https://openai.com/index/introducing-gpt-5/
- Anthropic (2025). Introducing Claude Opus 4.5. 2025-11-24. https://www.anthropic.com/news/claude-opus-4-5