
如果学到了这里,相信大部分人对Prompt并不陌生了。
在 Spring AI 的世界里,与强大的语言模型进行交互的基石便是 Prompt(提示语)。它不仅仅是你输入给 AI 的一段文本,更是你与智能对话的桥梁,是你唤醒模型潜能的关键指令。理解 Prompt 的本质、构建原则以及在 Spring AI 中的应用,是每一位希望有效利用 AI 能力的开发者必须掌握的核心技能。
Prompt是指导AI 模型生成特定输出的输入,一个有效的 Prompt 并非简单的提问,而是指令与上下文的巧妙融合。它告诉模型你期望它做什么(指令),并为其提供完成任务所需的背景信息(上下文)。模型接收到 Prompt 后,会基于其庞大的知识库和对语言的理解,生成符合你指令和上下文的输出。
通常我们使用Prompt会在ChatClient调用call方法时,传入一个prompt然后得到相应的ChatResponse返回。如我们前面使用到的:
UserMessage userMessage = new UserMessage(text);
String content = chatClient.prompt(new Prompt(userMessage)).call().content();Prompt 类其实本身并没有什么太多的内容。从上面示例代码中我们可以看出Prompt其实只是封装了一系列Message,而真正传入给AI的提示词其实是封装在了UserMessage中。没错的,从org.springframework.ai.chat.prompt.Prompt源码我们也可以看出:
public class Prompt implements ModelRequest<List<Message>> {
/**
* 返回构成 Prompt 的消息列表。
* 对于聊天模型,这通常包含用户消息、系统消息和助手消息。
* 对于文本生成或嵌入模型,可能只有一个或多个用户消息。
* @return 消息列表
*/
private final List<Message> messages;
/**
* 返回与 Prompt 相关的选项。
* 这些选项可能包含模型特定的参数,例如温度、最大 Token 数等。
*/
@Nullable
private ChatOptions chatOptions;
......
/**
* 返回 Prompt 的文本表示,这可能是将消息列表组合成单个字符串的结果。
* 不同的模型可能需要不同格式的文本输入。
* @return Prompt 的文本内容,如果没有则返回 null 或空字符串
*/
public String getContents() {
StringBuilder sb = new StringBuilder();
for (Message message : getInstructions()) {
sb.append(message.getText());
}
return sb.toString();
}
}在层次结构的顶层,对 LLM 的请求和响应由两个接口 ModelRequest 和 ModelResponse 表示。顾名思义, ModelRequest 表示发送到 AI 模型的请求, ModelResponse 表示收到的响应。
Prompt 类充当一系列有组织的 Message 对象和请求 ChatOptions 的容器。每条 Message 在提示中都包含一个独特的角色,其内容和意图不同。这些角色可以包含各种元素,从用户查询到 AI 生成的对相关背景信息的响应。这种安排支持与 AI 模型进行复杂而详细的交互,因为提示是由多条消息构建的,每条消息都分配了在对话中扮演的特定角色。

而Message其实是个接口,它通过集成Content接口定义了 文本内容、元数据属性的集合和称为 MessageType 的分类。
public interface Content {
String getText();
Map<String, Object> getMetadata();
}
public interface Message extends Content {
// MessageType用来区分消息类型,如USER, SYSTEM,ASSISTANT等
MessageType getMessageType();
}官网给出的Spring AI Message 的类图:

SpringAI内置了几种Message的不同实现,用于表示不同角色的消息:
Prompt的支持可以帮我们处理一些简单,无需动态生成内容的场景,或给到AI大模型之前,我们就已经通过其他方式构建好了输入。但是,当我们需要构建一些复杂的,比如根据用户输入,或根据数据库查询的数据需要动态生成Prompt对象。这个时候我们可能就需要额外做一些工作来封装Prompt,但是Spring AI也帮我们考虑好了,它提供了PromptTemplate的支持。

PromptTemplate实现了三个接口:
Prompt对象,该对象可直接传递给ChatClient以生成响应。Message对象进行其他的相关操作。Spring AI 模块借助了由 Terence Parr 开发的第三方库 StringTemplate 引擎来构造和插值提示。这些子类进一步扩展了 PromptTemplate 类,但用法保持不变。
Prompt。助手消息代表 AI 模型之前的回复或输出。Prompt。FunctionCallMessage 用于指示 AI 模型需要调用一个预定义的函数。Prompt。系统消息用于设置 AI 模型的行为、角色、目标或提供全局上下文。三者的区别是:
特性 |
|
|
|
|---|---|---|---|
消息类型 | SystemMessage(设置模型行为和上下文) | AssistantMessage(模型之前的回复) | FunctionCallMessage(指示模型调用函数) |
模板用途 | 定义模型的角色、目标、全局指令 | 记录和传递模型在对话中的历史回复 | 指示模型需要调用的函数名称和参数 |
应用场景 | 初始化对话上下文,设定模型行为准则 | 构建多轮对话,保持对话连贯性 | 触发 AI 模型调用外部功能或工具 |
模板内容示例 | "你是一个专业的{领域}助手。" | "你之前的回答是:{previous_answer}。" | {"name": "{name}", "arguments": "{arguments}"} |
使用PromptTemplate也很简单:
@GetMapping("/promptTemplate")
public String promptTemplate() {
// 使用系统消息预定好角色风格
SystemPromptTemplate systemTemplate = new SystemPromptTemplate ("你是一位风格{style}的演说家");
Message systemMessage = systemTemplate.createMessage(Map.of("style", "幽默诙谐"));
PromptTemplate userTemplate = new PromptTemplate("请介绍一下自己的{store}");
Message userMessage = userTemplate.createMessage(Map.of("store", "情感经历"));
List<Message> messages = List.of(systemMessage, userMessage);
String content = chatClient.prompt(new Prompt(messages)).call().content();
System.out.println(content);
return content;
}如果有多个PromptTemplate需要组合,只需要将合并进一个List即可。
4.1章节的示例代码,是将提示词写死在了Java代码中,而Spring AI允许将 prompt 数据放在 Resource 文件中,并直接注入到 PromptTemplate 中。在使用它之前,我们先来看一下他的核心源码实现,org.springframework.ai.chat.prompt.PromptTemplate:
这里省略掉部分代码,只留下关键核心代码:
public class PromptTemplate implements PromptTemplateActions, PromptTemplateMessageActions {
protected String template;
protected TemplateFormat templateFormat = TemplateFormat.ST;
private ST st;
private Map<String, Object> dynamicModel = new HashMap<>();
/**
* 可以根据Resource 方式创建一个PromptTemplate对象
* @param resource
*/
public PromptTemplate(Resource resource) {
// 将Resource资源文件中的数据读取出来
try (InputStream inputStream = resource.getInputStream()) {
this.template = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
}
catch (IOException ex) {
throw new RuntimeException("Failed to read resource", ex);
}
try {
// 构建StringTemplate实例
this.st = new ST(this.template, '{', '}');
}
catch (Exception ex) {
throw new IllegalArgumentException("The template string is not valid.", ex);
}
}
/**
* 可以根据Resource 方式创建一个PromptTemplate对象
* @param template
*/
public PromptTemplate(String template) {
this.template = template;
// If the template string is not valid, an exception will be thrown
try {
// 构建StringTemplate实例
this.st = new ST(this.template, '{', '}');
}
catch (Exception ex) {
throw new IllegalArgumentException("The template string is not valid.", ex);
}
}
public PromptTemplate(String template, Map<String, Object> model) {
this.template = template;
// If the template string is not valid, an exception will be thrown
try {
this.st = new ST(this.template, '{', '}');
for (Entry<String, Object> entry : model.entrySet()) {
add(entry.getKey(), entry.getValue());
}
}
catch (Exception ex) {
throw new IllegalArgumentException("The template string is not valid.", ex);
}
}
@Override
public String render() {
validate(this.dynamicModel);
return this.st.render();
}
/**
* 最终封装message时,会调用该方法进行渲染
* @return
*/
@Override
public String render(Map<String, Object> model) {
// 校验传入的变量,与占位符是否一致。如果不一致则不通过
validate(model);
for (Entry<String, Object> entry : model.entrySet()) {
if (this.st.getAttribute(entry.getKey()) != null) {
this.st.remove(entry.getKey());
}
// 读取资源文件
if (entry.getValue() instanceof Resource) {
this.st.add(entry.getKey(), renderResource((Resource) entry.getValue()));
}
else {
this.st.add(entry.getKey(), entry.getValue());
}
}
// 最终调用render生成提示词
return this.st.render();
}
/**
* promptTemplate生成提示词
*/
@Override
public Message createMessage(Map<String, Object> model) {
return new UserMessage(render(model));
}
private String renderResource(Resource resource) {
try {
return resource.getContentAsString(Charset.defaultCharset());
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
protected void validate(Map<String, Object> model) {
Set<String> templateTokens = getInputVariables();
Set<String> modelKeys = getModelKeys(model);
// Check if model provides all keys required by the template
if (!modelKeys.containsAll(templateTokens)) {
templateTokens.removeAll(modelKeys);
throw new IllegalStateException(
"Not all template variables were replaced. Missing variable names are " + templateTokens);
}
}
}当我们封装好的PromptTemplate调用createMessage生成提示词时,调用的就是createMessage() -> render() -> st.render() -> StringWriter -> write()。
搞懂了他的渲染逻辑后,我们可以来尝试一下了。我们定义一份user-message.st文件,用来存放我们的提示词。
user-message.st:
讲两个冷笑话,笑话的主题分别是{subject1}和{subject2}注入resource后,直接传给PromptTemplate:
@RestController
public class PromptTemplateWithSTController {
@Value("classpath:/templates/user-message.st")
private Resource promptUserMessage;
private final ChatClient chatClient;
public PromptTemplateWithSTController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@GetMapping("/st")
public String st() {
PromptTemplate promptTemplate = new PromptTemplate(promptUserMessage);
Prompt prompt = promptTemplate.create(Map.of("subject1", "程序员", "subject2", "打工人"));
return chatClient.prompt(prompt)
.call().content();
}
}显示结果:

其实感觉也没有那么玄乎。就是内置了一个字符串模板引擎生成对应的提示词。
如果想要改善AI的结果,光知道Prompt的使用是不够的。生成好的一个提示词,直接影响AI的输出有效性和质量。Spring AI也给出了一些建议,用来帮助我们生成有效的Prompt:
在开发提示时,重要的是要集成几个关键组件以确保清晰度和有效性:
以下是总结的一些常用的提示词技术:
1. Simple Techniques(简单技术)
2. Advanced Techniques (高级技术)
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。