提示工程模式
本教程共 45 篇 · 第 42 篇 · 更新于 2026-08-16 · 约 11 分钟阅读
本节目标:掌握提示工程的核心技术与参数配置。学完你能针对不同任务选对提示策略,并用 ChatClient 写出可运行的实现。
42.1 提示工程是门手艺
同样的模型,提示词不同,效果天差地别。提示工程就是研究怎么写提示词,让模型稳定输出你想要的结果。
本章内容基于 Google 的《提示工程综合指南》(Kaggle 白皮书)。官方文档把它翻译成了 Spring AI 的 Java 实现,示例代码在 spring-ai-examples 仓库的 prompt-engineering-patterns 目录。
先讲参数配置,再逐个讲技术。因为很多技术依赖参数配合,比如低温度适合分类,高温度适合头脑风暴。
42.2 输出配置
模型的行为由一组参数控制,统称 ChatOptions。用 builder 设置,既可以编程式配置,也可以写进 application.properties。
**温度(temperature)**控制随机性。低值(0.0-0.3)输出确定,适合分类和事实问答。高值(0.8-1.0)输出多样,适合创意写作。中值(0.4-0.7)通用。
.options(ChatOptions.builder()
.temperature(0.1)) // 高度确定性的输出
maxTokens 限制输出长度。5-25 适合单词和分类标签,50-500 适合段落,1000+ 适合长文。
Top-K 与 Top-P 是采样控制。Top-K 只看概率最高的前 K 个候选 token;Top-P 动态选取累计概率超过 P 的最小 token 集合。
.options(ChatOptions.builder()
.topK(40) // 只看前 40 个候选
.topP(0.8)) // 覆盖 80% 概率质量的集合
还有结构化响应。.content() 拿文本,.entity() 直接映射 Java 对象:
enum Sentiment { POSITIVE, NEUTRAL, NEGATIVE }
Sentiment result = chatClient.prompt("...")
.call()
.entity(Sentiment.class);
Note2.0 里 ChatClient 的 .options() 接收的是 builder,不是构建完的实例。直接传 ChatOptions.builder() 即可。
每个厂商还有专属选项,比如 OpenAI 的 frequencyPenalty、Anthropic 的 thinkingEnabled。用专属选项会绑定厂商,牺牲可移植性,按需取舍。
42.3 零样本提示
零样本(Zero-Shot)不给任何示例,直接描述任务。模型靠训练时见过的大量任务范例来理解指令。
适合简单任务和想控制提示长度的场景。性能取决于任务复杂度和指令表述。
public void pt_zero_shot(ChatClient chatClient) {
enum Sentiment { POSITIVE, NEUTRAL, NEGATIVE }
Sentiment reviewSentiment = chatClient.prompt("""
将影评分类为 POSITIVE、NEUTRAL 或 NEGATIVE。
影评:《Her》是一部令人不安的研究,揭示了如果任由 AI 继续不受约束地进化,
人类将走向何方。我希望这样的杰作能更多一些。
情感倾向:
""")
.options(ChatOptions.builder()
.model("claude-sonnet-4-20250514")
.temperature(0.1)
.maxTokens(5))
.call()
.entity(Sentiment.class);
}
注意两个细节:温度 0.1 压随机性,maxTokens 5 只够输出一个标签,.entity() 直接映射枚举。
42.4 单样本与少样本提示
少样本(Few-Shot)给模型看示例。展示输入输出对,模型照葫芦画瓢,不需要更新参数。
单样本只给一个示例,适合示例昂贵或模式简单的场景。少样本通常给 3-5 个,适合复杂任务和格式要求严格的场景。
public void pt_one_shot_few_shots(ChatClient chatClient) {
String pizzaOrder = chatClient.prompt("""
把顾客的披萨订单解析成合法 JSON
示例 1:
我想要一个小号披萨,加奶酪、番茄酱和意大利辣香肠。
JSON 响应:
```
{
"size": "small",
"type": "normal",
"ingredients": ["cheese", "tomato sauce", "pepperoni"]
}
```
示例 2:
能给我一个大号披萨吗?加番茄酱、罗勒和马苏里拉奶酪。
JSON 响应:
```
{
"size": "large",
"type": "normal",
"ingredients": ["tomato sauce", "basil", "mozzarella"]
}
```
现在,我想要一个大号披萨:前半加奶酪和马苏里拉奶酪,
另一半加番茄酱、火腿和菠萝。
""")
.options(ChatOptions.builder()
.model("claude-sonnet-4-20250514")
.temperature(0.1)
.maxTokens(250))
.call()
.content();
}
示例的质量直接影响效果。示例要覆盖边界情况,比如这里第二个示例展示了另一种配料组合。示例选得差,模型会学歪。
42.5 系统提示词与角色提示
系统提示词(System Prompt)设定全局框架。它独立于具体问题,定义输出格式、语气、伦理边界,是整个对话的”任务声明”。
String movieReview = chatClient
.prompt()
.system("将影评分类为 positive、neutral 或 negative,只返回大写标签。")
.user("""
影评:《Her》是一部令人不安的研究,揭示了如果任由 AI 继续不受约束地进化,
人类将走向何方。它太令人不安了,我都没能看完。
情感倾向:
""")
.call()
.content();
系统提示词配合实体映射很强大。让模型返回 JSON,再用 record 接收:
record MovieReviews(Movie[] movie_reviews) {
enum Sentiment { POSITIVE, NEUTRAL, NEGATIVE }
record Movie(Sentiment sentiment, String name) {}
}
MovieReviews movieReviews = chatClient
.prompt()
.system("将影评分类为 positive、neutral 或 negative,返回合法 JSON。")
.user("""
影评:《Her》是一部令人不安的研究……
JSON 响应:
""")
.call()
.entity(MovieReviews.class);
角色提示(Role Prompting)让模型扮演特定身份。专家、导游、莎士比亚,不同的角色改变回答的风格与深度。
String travelSuggestions = chatClient
.prompt()
.system("""
请你扮演一名旅行导游。我会告诉你我所在的位置,
你要推荐 3 个附近值得去的地方。有些时候,我还会告诉你
我想去的场所类型。
""")
.user("我的情况:我在阿姆斯特丹,只想参观博物馆。\n旅行建议:")
.call()
.content();
角色提示适合专业领域问答,能保持跨对话的语气一致。
42.6 上下文提示
上下文提示(Contextual Prompting)通过参数注入背景信息。Spring AI 用 param() 方法,干净利落:
String articleSuggestions = chatClient
.prompt()
.user(u -> u.text("""
推荐 3 个可以写文章的主题,并附上几行说明,
描述这篇文章应该包含什么内容。
上下文:{context}
""")
.param("context", "你在为一个关于 80 年代复古街机游戏的博客写作。"))
.call()
.content();
模板里留 {context} 占位符,运行时用 param() 填值。上下文与主指令分离,模板可以复用。
42.7 分步回溯
分步回溯(Step-Back Prompting)先问背景知识,再解决具体问题。模型先”退一步”想清楚一般原理,再回到原题。
public void pt_step_back_prompting(ChatClient.Builder chatClientBuilder) {
var chatClient = chatClientBuilder
.defaultOptions(ChatOptions.builder()
.model("claude-sonnet-4-20250514")
.temperature(1.0)
.topK(40)
.topP(0.8)
.maxTokens(1024))
.build();
// 第一步:先获取高层概念
String stepBack = chatClient
.prompt("""
基于流行的第一人称射击游戏,有哪些虚构的关键设定,
能让第一人称射击游戏的关卡剧情既有挑战性又引人入胜?列出 5 个。
""")
.call()
.content();
// 第二步:把概念作为上下文,完成主任务
String story = chatClient
.prompt()
.user(u -> u.text("""
为第一人称射击游戏的一个新关卡写一段剧情梗概,
要既有挑战性又引人入胜。
上下文:{step-back}
""")
.param("step-back", stepBack))
.call()
.content();
}
两个 ChatClient 调用,两次交互。适合复杂推理和需要专业知识的场景,代价是延迟翻倍。
42.8 思维链
思维链(Chain of Thought,CoT)让模型逐步推理。关键短语是”Let’s think step by step”,触发模型展示中间推理过程。
零样本版:
String output = chatClient
.prompt("""
我 3 岁时,我的搭档年龄是我的 3 倍。现在我 20 岁了,
我的搭档多大?
让我们一步一步地思考。
""")
.call()
.content();
少样本版更稳,先给一个完整的推理示例:
String output = chatClient
.prompt("""
问:我哥哥 2 岁时,我的年龄是他的 2 倍。现在我 40 岁了,
我哥哥多大?让我们一步一步地思考。
答:我哥哥 2 岁时,我是 2 * 2 = 4 岁。我们相差 2 岁,我比他大。
现在我 40 岁,所以哥哥是 40 - 2 = 38 岁。答案是 38。
问:我 3 岁时,我的搭档年龄是我的 3 倍。现在我 20 岁了,
我的搭档多大?让我们一步一步地思考。
答:
""")
.call()
.content();
思维链对数学题、逻辑推理特别有效。中间步骤显式化之后,模型出错率明显下降。
Tip思维链适合需要展示过程的场景,但会拉长输出。生产环境注意 maxTokens 要留够推理空间。
42.9 自我一致性
自我一致性(Self-Consistency)是思维链的升级版。同一问题跑多次,每次用高温度制造差异,最后多数表决。
看官方示例:判断邮件是否重要,跑 5 次,统计票数。
record EmailClassification(Classification classification, String reasoning) {
enum Classification { IMPORTANT, NOT_IMPORTANT }
}
int importantCount = 0;
int notImportantCount = 0;
// 同一输入跑 5 次
for (int i = 0; i < 5; i++) {
EmailClassification output = chatClient
.prompt()
.user(u -> u.text("""
邮件:{email}
把上面的邮件分类为 IMPORTANT 或 NOT IMPORTANT。请一步一步地
思考并说明理由。
""")
.param("email", email))
.options(ChatOptions.builder()
.temperature(1.0)) // 高温度制造变化
.call()
.entity(EmailClassification.class);
if (output.classification() == EmailClassification.Classification.IMPORTANT) {
importantCount++;
} else {
notImportantCount++;
}
}
String finalClassification = importantCount > notImportantCount
? "IMPORTANT" : "NOT IMPORTANT";
相当于给 LLM 输出做集成。适合高风险决策,代价是 5 倍的成本和延迟。
42.10 思维树与自动提示工程
思维树(Tree of Thoughts,ToT)是思维链的进一步扩展。它同时探索多条推理路径,评估每条路径的前景,再沿最有希望的路径深入。
官方文档没有完整实现,只给了简化示例:生成多个候选开局走法、评估选最优、再投影后续局面。三步都是独立调用,相当于手动实现搜索树。
自动提示工程(Automatic Prompt Engineering,APE)用 AI 优化提示词。先生成同一请求的多个变体,再用 BLEU 等指标评估,选出最好的。
// 第一步:生成 10 个语义相同的变体
String orderVariants = chatClient
.prompt("""
我们有一家乐队周边 T 恤网店,为了训练聊天机器人,
需要"一件 S 码 Metallica T 恤"这句话的多种下单说法。生成 10 个
语义相同的变体,保持含义不变。
""")
.options(ChatOptions.builder().temperature(1.0))
.call()
.content();
// 第二步:评估并选择最优变体
String output = chatClient
.prompt()
.user(u -> u.text("""
请对以下变体进行 BLEU 评估:
----
{variants}
----
选出评估得分最高的指令候选。
""")
.param("variants", orderVariants))
.call()
.content();
APE 适合生产环境批量优化提示词,是”用 AI 改进 AI”的元技术。
42.11 代码提示
代码提示是针对编程任务的专门技术。写代码、解释代码、翻译代码,三件套。
写代码的提示词要点:规格要清楚,上下文要够,温度要低。
String bashScript = chatClient
.prompt("""
用 Bash 写一段代码:先询问文件夹名称,
然后读取文件夹里的内容,把所有文件名前面加上 draft 前缀。
""")
.options(ChatOptions.builder().temperature(0.1))
.call()
.content();
解释代码用 {code} 占位符传代码块,翻译代码同理,让模型输出目标语言版本。代码任务建议温度 0.1-0.3,确定性优先。
42.12 小结
单一技术很少够用。官方文档的建议是组合:系统提示词 + 少样本示例、思维链 + 角色提示,效果往往更好。
生产环境还有几条经验:用不同参数测试提示词、关键决策用自我一致性、优先用 entity() 拿类型安全响应、用上下文提示注入业务知识。
十种技术各有定位。零样本和少样本管格式,系统/角色/上下文管框架,分步回溯和思维链管推理,自我一致性管可靠性,思维树和 APE 管复杂任务,代码提示管编程。
下一章讲动态工具发现。提示词决定模型怎么想,工具决定模型能做什么。