首页 / Spring AI 入门教程 / 提示工程模式

Spring AI 入门教程

提示工程模式

本教程共 45 篇 · 第 42 篇 · 更新于 2026-08-16 · 约 11 分钟阅读

Spring AI提示工程Prompt EngineeringFew-Shot思维链CoTSelf-ConsistencyChatOptions

本节目标:掌握提示工程的核心技术与参数配置。学完你能针对不同任务选对提示策略,并用 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);
Note

2.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 管复杂任务,代码提示管编程。

下一章讲动态工具发现。提示词决定模型怎么想,工具决定模型能做什么。