乐于分享
好东西不私藏

Javaer的AI学习之路——Spring AI 入门:(4)提示词、提示词模板、提示词工程与模型参数

Javaer的AI学习之路——Spring AI 入门:(4)提示词、提示词模板、提示词工程与模型参数

本章导读:你向AI提问的方式,直接决定了它回答的质量。就像与人沟通一样,把需求表达得越清楚,对方理解得就越准确。在Spring AI中,提示词的设计和构造有着一套完整的体系——从基础的四种消息角色,到动态的提示模板,再到工程化的最佳实践。本章将带你系统掌握这些知识,让你从“随意提问”进阶到“专业引导”。

4.1 提示词的本质

4.1.1 什么是Prompt

Prompt(提示词)是我们与AI沟通的唯一方式。你可以把它理解为给AI下达的指令或提出的问题——就像和人沟通一样,你说得越清楚,对方理解得就越准确,Prompt写得好不好,直接决定了AI输出的质量。

一个有效的Prompt并非简单的提问,而是指令与上下文的巧妙融合。它告诉模型你期望它做什么(指令),并为其提供完成任务所需的背景信息(上下文)。模型接收到Prompt后,会基于其庞大的知识库和对语言的理解,生成符合你指令和上下文的输出。

从API层面来看,Spring AI中的Prompt并非简单的字符串,而是一个容器。Prompt类充当一系列有组织的Message对象和请求ChatOptions的容器。每条Message在提示中都包含一个独特的角色,其内容和意图不同。这些角色可以包含各种元素,从用户查询到AI生成的对相关背景信息的响应。这种安排支持与AI模型进行复杂而详细的交互。

4.1.2 Prompt的组成要素

一个完整的Prompt在Spring AI中由两个核心部分组成:

  • 消息列表(List<Message>:包含系统消息、用户消息、助手消息等,每条消息都有特定的角色。

  • ChatOptions:控制模型行为的参数,如temperaturemaxTokens等。

4.1.3 四种Message角色详解

在Spring AI中,消息被抽象为几个类,每种类型对应对话中的一个特定角色,“同一句话,放在不同消息角色里,效果可能完全不同”——Prompt的本质不是提问,而是精确表达任务。

消息类型
说明
典型用途
SystemMessage
系统提示词,设定AI角色和行为
“你是一个专业的Java技术专家”
UserMessage
用户消息,实际的问题或指令
“什么是Spring AI?”
AssistantMessage
AI的回复消息
记录对话历史
ToolResponseMessage
工具调用返回的结果
天气查询结果

SystemMessage(系统消息) 是最重要也最容易被忽视的消息类型。它在对话开始前设定AI的身份、行为准则、输出风格或知识边界,通常用户不可见,但深刻影响AI的所有响应。简而言之,SystemMessage告诉模型“你是谁”和“你应该怎么做”。

UserMessage(用户消息) 承载用户的真实需求——问题、指令或陈述,这是AI生成响应的直接依据。

AssistantMessage(助理消息) 代表AI模型对之前消息的回复,主要用于构造多轮对话历史。

ToolResponseMessage(工具响应消息) 将工具执行的结果反馈给AI,供其生成最终回答。框架的@Tool注解已经封装了这一细节,开发者通常无需直接操作。

理解这些消息类型是构造高质量Prompt的基础。SystemMessage设定角色和边界,UserMessage提出具体任务,两者分工明确,切忌将所有内容混入UserMessage。

4.2 提示工程基础

提示工程(Prompt Engineering)是指通过精心设计提示词来优化AI模型输出效果的一套方法论。以下是三种核心的提示工程技术。

4.2.1 零样本提示(Zero-shot Prompting)

零样本提示是最基础的提示模式:直接向AI模型发出指令,不提供任何示例,让模型依靠其训练时获得的知识来理解和执行任务。第三章中的所有示例都属于零样本提示。

@GetMapping("/chat")public String chat(@RequestParam(defaultValue = "你好") String msg) {return chatClient.prompt()            .user(msg)            .call()            .content();}

4.2.2 少样本提示(Few-shot Prompting)

与零样本相反,少样本提示在发出指令的同时提供一两个示例,让模型参考示例的格式和风格来生成输出。这种方式在需要模型模仿特定输出格式时特别有效。

以下示例展示了如何让AI输出固定格式的回答:

@GetMapping("/classification")public String classifySentiment(@RequestParam String review) {return chatClient.prompt()            .system("""                    你是一个情感分析助手。将用户评论分类为「好评」「中评」或「差评」。                    示例:                    输入:"这个产品质量太差了,完全不符合预期"                    输出:差评                    输入:"物超所值,非常满意!"                    输出:好评                    输入:"还行吧,没有特别惊艳但也不差"                    输出:中评                    """)            .user(review)            .call()            .content();}

4.2.3 思维链提示(Chain-of-Thought)

对于复杂推理问题,思维链提示通过引导模型“一步一步思考”来提升推理准确性。在提示中要求模型展示推理过程,而非直接给出结论。

@GetMapping("/reasoning")public String solveProblem(@RequestParam String problem) {return chatClient.prompt()            .system("""                     你是一个逻辑推理专家。对于复杂问题,请按以下步骤分析:                    1. 拆解问题的关键要素                    2. 逐步推理每个要素的逻辑                    3. 综合得出结论                    请一步一步分析,不要直接给出最终答案。                    """)            .user(problem)            .call()            .content();}

4.2.4 系统提示词的设计原则

一个高质量的SystemMessage通常包含多个关键要素,常见的设计框架是“角色+任务+格式+示例”的四段式结构。

要素
说明
示例
角色
告诉模型“你是谁”
“你是一个专业的Java技术助手”
任务
你要做什么
“回答技术问题、帮助debug代码”
约束
什么能做、什么不能做
“不确定的内容要说明,不要编造”
格式
用什么格式返回
“使用Markdown格式,代码用代码块包裹”
示例
给一两个示例让模型参考
Few-shot Prompting

在代码中,可以通过ChatClient.BuilderdefaultSystem()来配置默认的系统消息。这样配置后,这段文本会作为基础角色设定,注入到每次对话的上下文中。

@RestControllerpublicclassSystemPromptController {privatefinal ChatClient chatClient;publicSystemPromptController(ChatClient.Builder builder) {this.chatClient = builder                .defaultSystem("""                    你是一个专业的Java技术助手。                    职责:                    - 回答Java、Spring Boot相关的技术问题                    - 帮助用户理解代码原理                    - 提供最佳实践建议                    规则:                    - 代码示例使用Java 17+语法                    - 回答简洁,不要过度解释                    - 不确定的内容要说明,不要编造                    """)                .build();    }@GetMapping("/ask")public String ask(@RequestParam String question) {return chatClient.prompt()                .user(question)                .call()                .content();    }}

4.3 使用PromptTemplate动态构造提示

在实际开发中,我们经常需要根据不同的用户输入动态构造提示词。如果不用模板,每个问题都要写一个新的请求,代码会变得重复、难以维护。

PromptTemplate正是为解决这个问题而设计。使用模板,你可以定义一套包含占位符的提示结构,运行时动态替换占位符的值,实现“一套模板,复用多个场景”。

4.3.1 基本语法

PromptTemplate是Spring AI中用于创建带变量模板的Prompt的核心类,使用{}包裹占位符:

// 1. 创建模板,使用{}包裹占位符PromptTemplatetemplate=newPromptTemplate("你好,我叫{name},今年{age}岁");// 2. 填充变量,Map的key对应模板中的占位符Promptprompt= template.create(Map.of("name""张三""age""25"));// 3. 调用AIStringresult= chatClient.prompt(prompt).call().content();// 输出:你好,我叫张三,今年25岁

4.3.2 在ChatClient中直接使用模板

ChatClient也支持在链式调用中直接使用模板,语法更加简洁:

@GetMapping("/introduce")public String introduce(@RequestParam String name, @RequestParamint age) {return chatClient.prompt()            .user(u -> u.text("你好,我叫{name},今年{age}岁")                .param("name", name)                .param("age", age))            .call()            .content();}

4.3.3 SystemPromptTemplate:系统消息专用模板

SystemPromptTemplate是专门用于创建系统消息的模板,用法与PromptTemplate几乎一致:

// 创建系统消息模板SystemPromptTemplatesystemTemplate=newSystemPromptTemplate("你是一个{profession}专家,擅长{skill}");// 填充变量,生成SystemMessageMessagesystemMessage= systemTemplate.createMessage(Map.of("profession""Java","skill""后端开发"));// 输出:你是一个Java专家,擅长后端开发

4.3.4 从外部文件加载模板

将模板内容从代码中分离到外部文件,是一种良好的工程实践。模板与代码解耦,便于维护和版本控制。

步骤一:创建模板文件

src/main/resources/prompts/目录下创建模板文件qa-template.txt

请基于以下背景信息回答问题。【背景信息】{context}【问题】{question}【要求】- 如果背景信息中包含答案,请基于背景信息回答- 如果背景信息中不包含答案,请明确告知用户"根据现有信息无法回答"- 回答要简洁、准确

步骤二:在Java代码中加载

import org.springframework.core.io.ClassPathResource;import org.springframework.ai.chat.prompt.PromptTemplate;@GetMapping("/rag-answer")public String answerWithTemplate(@RequestParam String context,@RequestParam String question)throws IOException {// 从resources/prompts/qa-template.txt加载模板Resourceresource=newClassPathResource("prompts/qa-template.txt");StringtemplateContent= resource.getContentAsString(StandardCharsets.UTF_8);PromptTemplatepromptTemplate=newPromptTemplate(templateContent);Promptprompt= promptTemplate.create(Map.of("context", context,"question", question    ));return chatClient.prompt(prompt).call().content();}

这种模板文件化管理方式将提示词与Java代码分离,对于提示词较长、格式复杂或需要频繁调整的场景,强烈推荐使用。

4.4 模型参数详解

在发起请求与大模型交互时,可以配置各类核心参数,以此控制模型的生成行为、回复长度、随机性等关键特性。

ChatOptions是Spring AI中管理这些配置的核心接口,它继承了ModelOptions根接口。ChatOptions除了一个默认实现类DefaultChatOptions外,还有两个子接口:ToolCallingChatOptions(工具调用配置)和StructuredOutputChatOptions(结构化输出配置)。

4.4.1 核心参数说明

参数
作用
取值范围
推荐场景
temperature
控制输出随机性,越低越确定,越高越有创意
0~2(不同模型范围略有差异)
代码生成用0.10.3,创意写作用0.81.0
maxTokens
限制模型输出的最大Token数量
取决于模型上下文窗口
短问答用500,长内容生成用2000+
topP
核采样,选择概率和质量达到P的token集
0~1
通常与temperature配合,推荐调整其中一个即可
frequencyPenalty
惩罚重复token,抑制模型重复内容
-2~2
需要多样化输出时可设为0.5~1.0
presencePenalty
惩罚已出现过的主题,鼓励引入新话题
-2~2
需要探索新话题时可设为0.5~1.0

temperature(温度)详解:温度参数可能是影响模型输出最直观的配置。低温度(0.1-0.3)使模型倾向于选择高概率的token,输出更确定、更稳定;高温度(0.8-1.0)增加低概率token被选中的机会,输出更多样、更有创意。

maxTokens(最大输出长度):限制模型单次回复的Token数量上限,同时直接影响API调用成本(按Token计费)。

4.4.2 在ChatClient中配置参数

ChatClient提供了链式的参数配置方式:

@GetMapping("/code")public String generateCode(@RequestParam String requirement) {return chatClient.prompt()            .user(requirement)            .options(ChatOptions.builder()                .temperature(0.2D)      // 低温度保证代码准确                .maxTokens(1000)        // 限制输出长度                .topP(0.95D)            // 核采样                .build())            .call()            .content();}

也可以通过ChatOptions.builder快速构建配置,兼容所有厂商:

ChatOptionsoptions= ChatOptions.builder()    .model("qwen-plus")           // 指定模型    .temperature(0.1D)            // 低随机性,适合精准回答    .maxTokens(1024)              // 最大输出1024 Token    .topP(0.9D)                   // 核采样参数    .stopSequences(List.of("###")) // 停止词    .build();

4.4.3 厂商专属配置

每个大模型厂商都有自己独有的API参数,Spring AI为特定厂商大模型提供了不同的ChatOptions实现类。例如,如果使用通义千问并通过DashScope OpenAI兼容端点接入,标准ChatOptions即可满足大部分需求;如果直接使用其他厂商的专有SDK,可以使用对应的专属配置类。

4.5 综合实战:可配置的智能问答助手

现在,让我们将本章所学内容整合起来,构建一个完整的可配置智能问答助手。这个助手支持:

  • 动态系统提示词(用户可指定AI角色)
  • 模型参数可调节
  • 从外部模板文件加载提示词
  • 支持多轮对话参数配置

4.5.1 完整代码实现

@RestController@RequestMapping("/configurable")publicclassConfigurableChatController {privatefinal ChatClient chatClient;publicConfigurableChatController(ChatClient.Builder builder) {this.chatClient = builder.build();    }/**     * 核心问答接口     * @param question 用户问题     * @param systemPrompt 可选系统提示词,默认值为"你是一个乐于助人的AI助手"     * @param role AI角色,用于模板替换     * @param temperature 温度参数,控制输出随机性,默认0.7     * @param maxTokens 最大输出Token数,默认500     */@GetMapping("/ask")public String ask(@RequestParam String question,@RequestParam(defaultValue = "你是一个乐于助人的AI助手") String systemPrompt,@RequestParam(defaultValue = "普通助手") String role,@RequestParam(defaultValue = "0.7")double temperature,@RequestParam(defaultValue = "500")int maxTokens) {// 方式1:直接使用传入的系统提示词// 方式2:也可以从模板文件中加载系统提示词StringfinalSystemPrompt="你是一个" + role + "。" + systemPrompt;return chatClient.prompt()                .system(finalSystemPrompt)                .user(question)                .options(ChatOptions.builder()                    .temperature(temperature)                    .maxTokens(maxTokens)                    .build())                .call()                .content();    }/**     * 使用模板文件的问答接口(演示外部模板加载)     */@GetMapping("/ask-with-template")public String askWithTemplate(@RequestParam String role,@RequestParam String topic,@RequestParam String question)throws IOException {// 从外部文件加载系统消息模板Resourceresource=newClassPathResource("prompts/system-template.txt");StringsystemTemplateStr= resource.getContentAsString(StandardCharsets.UTF_8);SystemPromptTemplatesystemTemplate=newSystemPromptTemplate(systemTemplateStr);// 创建系统消息MessagesystemMessage= systemTemplate.createMessage(Map.of("role", role,"topic", topic        ));// 构建完整的Prompt并调用return chatClient.prompt()                .messages(systemMessage)                .user(question)                .options(ChatOptions.builder()                    .temperature(0.7)                    .maxTokens(800)                    .build())                .call()                .content();    }}

其中src/main/resources/prompts/system-template.txt代码如下:

你是一个{role},专注于{topic}领域。你的回答风格:- 专业、准确、简洁- 使用中文回答- 如果需要代码示例,请使用适当的代码块格式请基于以上角色和领域知识,回答用户的问题。

4.5.2 测试示例

测试1:零样本问答

GET /configurable/ask?question=什么是Spring AI&role=Java专家&temperature=0.2

输出结果会更确定、更专业,适合技术问答场景。

测试2:带创意的回答

GET /configurable/ask-with-template?question=讲一下Java面向对象&role=Java专家&topic=编程

输出结果按照你的文件的风格展示。

4.6 常见误区与最佳实践

4.6.1 ❌ 误区一:把控制参数写进提示词

很多人写Prompt时喜欢加这些句子:

  • “请保持稳定输出”
  • “不要超过200字”
  • “回答要随机一些”

这些说法不是完全没用,但Spring AI提供了更专业的控制方式——通过ChatOptions配置temperaturemaxTokens等参数来控制生成行为。上面的示例已给出正确示范。

4.6.2 ❌ 误区二:提示词和代码不分家

当提示词内容较长时,散落在Java代码字符串中既不便于维护,也难以协作。正确的做法是将提示词模板抽取到外部文件(src/main/resources/prompts/目录下)或数据库中,实现提示词与代码的分离。

4.6.3 ✅ 良好提示词的标准

一个工程化良好的Prompt在代码中应具备以下特征:

┌─────────────────────────────────────────────────────┐│ SystemMessage: 角色 + 任务 + 约束 + 格式            │├─────────────────────────────────────────────────────┤│ UserMessage: 具体问题(支持参数化)                 │├─────────────────────────────────────────────────────┤│ ChatOptions: temperature / maxTokens 等参数配置     │└─────────────────────────────────────────────────────┘

将这些要素拆开、参数化,而不是揉成一团堆在一个字符串里,这才是工程化的方式。

4.7 本章小结

在本章中,我们系统学习了Spring AI的提示词体系:

  • 四种消息类型SystemMessage设定AI角色,UserMessage承载用户任务,AssistantMessage记录AI回复,ToolResponseMessage反馈工具执行结果

  • 提示工程基础:零样本提示、少样本提示(通过示例让模型模仿)、思维链提示(引导模型一步步思考)

  • PromptTemplate:使用模板实现提示词参数化和复用,支持从外部文件加载,将提示词与Java代码分离

  • 模型参数temperature控制输出随机性,maxTokens限制输出长度,ChatOptions统一管理

  • 综合实战:构建了一个完整的可配置智能问答助手,演示了上述所有技术的整合使用

  • 最佳实践:区分系统消息和用户消息、使用ChatOptions控制参数、提示词与代码分离

提示词质量在很大程度上决定了AI应用的输出质量。从本章开始,你不再是“随意提问”的用户,而是能够“专业引导”AI的开发者。

下一章,我们将学习如何让AI输出结构化的数据——将模型返回的自然语言直接映射为Java对象,让AI的输出真正可以被业务代码直接消费。

参考来源

  • Spring AI官方Prompt API文档
  • Spring AI ChatClient源码分析
  • 阿里云DashScope API参数参考

项目源码请私信回复:Spring AI