1. 项目概述
作为一名长期深耕Java生态的技术开发者,我在实际项目中深刻体会到Prompt工程对于大模型应用的重要性。Spring AI作为Spring生态中对接大模型的桥梁,其Prompt体系设计直接影响着最终的应用效果。本文将基于实战经验,系统梳理Spring AI中Prompt工程的核心要点与最佳实践。
在开发基于大模型的Java应用时,我们常常会遇到这样的困境:同样的模型,不同开发者调用得到的结果质量差异巨大。这背后往往不是代码能力的差距,而是对Prompt工程的理解深度不同。Spring AI通过一套清晰的API设计,让我们能够以符合Java开发者习惯的方式构建高质量的Prompt。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心概念解析
2.1 Prompt的本质与价值
Prompt本质上是我们与大模型沟通的"语言"。就像人与人交流需要清晰表达需求一样,好的Prompt能让大模型准确理解我们的意图。在Spring AI中,Prompt是一个容器,封装了多条Message对象,这些Message共同构成了大模型的输入上下文。
从技术实现角度看,Spring AI的Prompt体系包含几个关键层级:
- 最底层是Message接口及其实现类,代表单条消息
- 中间层是Prompt容器,组织多条Message
- 上层是各种Template,提供动态生成Prompt的能力
2.2 Spring AI中的Message类型
Spring AI定义了多种Message类型,每种都有特定的使用场景:
| 消息类型 | 作用 | 典型使用场景 |
|---|---|---|
| SystemMessage | 设定AI角色和行为规则 | "你是一个Java专家,只回答技术问题" |
| UserMessage | 用户的具体问题或指令 | "请解释Spring Bean的生命周期" |
| AssistantMessage | 模型的历史回复 | 多轮对话中保存上下文 |
| ToolResponseMessage | 工具调用的返回结果 | 函数调用场景中使用 |
在实际开发中,SystemMessage和UserMessage是最常用的两种类型。SystemMessage必须放在Prompt的最前面,因为它定义了模型的行为准则,直接影响后续消息的处理方式。
3. 基础Prompt构造实战
3.1 手动组装Message
最基础的Prompt构造方式是手动创建各个Message对象。这种方式虽然代码量较多,但能让我们清晰理解Prompt的内部结构:
java复制@RestController
public class BasicPromptController {
@Resource(name = "deepseek")
private ChatModel chatModel;
@GetMapping("/story")
public String generateStory(String topic) {
// 创建系统消息 - 定义AI角色
SystemMessage systemMsg = new SystemMessage(
"你是一个讲故事的高手,每个故事控制在300字以内");
// 创建用户消息 - 具体请求
UserMessage userMsg = new UserMessage(
"讲一个关于" + topic + "的科幻故事");
// 组装Prompt - 注意顺序
Prompt prompt = new Prompt(List.of(systemMsg, userMsg));
// 调用模型
return chatModel.call(prompt).getResult().getOutput().getText();
}
}
这里有几个关键点需要注意:
- SystemMessage和UserMessage的顺序不能颠倒
- 使用List.of()明确指定消息顺序更可靠
- 每个Message的文本内容需要精心设计
3.2 使用ChatClient简化调用
Spring AI提供了更高级的ChatClient API,可以大幅简化Prompt构造过程:
java复制@GetMapping("/story-simple")
public Flux<String> generateStorySimple(String topic) {
return chatClient.prompt()
.system("你是一个讲故事的高手,每个故事控制在300字以内")
.user("讲一个关于" + topic + "的科幻故事")
.stream()
.content();
}
ChatClient的链式调用不仅代码更简洁,而且内部会自动处理Message的顺序问题。对于大多数简单场景,这是推荐的使用方式。
4. 动态模板实战
4.1 PromptTemplate基础使用
当Prompt需要动态内容时,字符串拼接的方式既不优雅也不安全。Spring AI提供了PromptTemplate来解决这个问题:
java复制@GetMapping("/dynamic-story")
public String dynamicStory(String genre, String length) {
// 定义模板
PromptTemplate template = new PromptTemplate(
"讲一个{genre}类型的故事,字数控制在{length}字左右");
// 填充参数
Prompt prompt = template.create(Map.of(
"genre", genre,
"length", length
));
return chatModel.call(prompt).getResult().getOutput().getText();
}
这种方式有三大优势:
- 模板与代码分离,更易维护
- 自动处理参数转义,避免注入风险
- 支持参数验证,确保必填参数都存在
4.2 外部模板文件
对于复杂的Prompt,建议将模板内容放在外部文件中:
- 在resources目录下创建模板文件,如
templates/story.txt:
code复制讲一个关于{topic}的{genre}故事。
要求:
- 字数在{length}字左右
- 使用{style}风格
- 包含{character}这个角色
- 代码中加载模板:
java复制@Value("classpath:/templates/story.txt")
private Resource storyTemplate;
@GetMapping("/external-template")
public String externalTemplate(Map<String, Object> params) {
PromptTemplate template = new PromptTemplate(storyTemplate);
Prompt prompt = template.create(params);
return chatModel.call(prompt).getResult().getOutput().getText();
}
这种方式实现了Prompt与代码的完全解耦,非技术人员也可以修改模板内容而无需重新部署代码。
5. 系统提示词工程
5.1 高质量系统提示词设计
SystemMessage的质量直接决定了大模型的表现。一个好的系统提示词应包含以下要素:
- 角色定义:明确AI的身份和专业领域
- 能力边界:清楚界定回答范围
- 输出要求:格式、长度、风格等约束
- 安全限制:防止滥用和越界行为
示例:专业技术助手的系统提示词
java复制String systemPrompt = """
你是一个资深的Java开发专家,专注于Spring生态系统。
你的职责包括:
1. 回答Java和Spring相关的技术问题
2. 提供可运行的代码示例
3. 拒绝回答非技术问题
回答要求:
- 专业准确,有技术深度
- 代码示例使用Markdown格式
- 对复杂概念提供类比解释
- 字数控制在500字以内
安全限制:
- 不执行任何系统命令
- 不泄露任何敏感信息
- 不讨论政治、宗教等敏感话题
""";
5.2 系统提示词模板化
对于需要动态内容的系统提示词,可以使用SystemPromptTemplate:
java复制SystemPromptTemplate sysTemplate = new SystemPromptTemplate("""
你是{domain}领域的专家,专注于{specialty}方向。
你的回答必须:
1. 使用{format}格式
2. 包含至少{exampleCount}个示例
3. 拒绝回答与{domain}无关的问题
""");
Message sysMessage = sysTemplate.createMessage(Map.of(
"domain", "Java后端开发",
"specialty", "高并发系统设计",
"format", "Markdown",
"exampleCount", "2"
));
这种方式特别适合需要根据不同场景动态调整AI角色的应用。
6. 生产环境最佳实践
6.1 提示词管理策略
在企业级应用中,建议采用以下策略管理Prompt:
- 版本控制:将Prompt模板纳入代码仓库,记录每次修改
- A/B测试:对重要Prompt进行多版本对比测试
- 性能监控:记录每个Prompt的响应时间、token消耗等指标
- 内容审核:对用户生成的内容进行安全过滤
6.2 常见问题排查
在实际开发中,我们遇到过这些典型问题:
-
Prompt不生效
- 检查SystemMessage是否在最前面
- 确认没有消息顺序错误
- 验证模板参数是否正确填充
-
中文乱码问题
- 确保模板文件使用UTF-8编码
- 检查Maven资源过滤配置
- 验证HTTP请求的Content-Type
-
参数未替换
- 检查占位符拼写是否一致
- 确认参数Map中包含所有必需key
- 验证是否有特殊字符需要转义
-
流式输出中断
- 检查Prompt长度是否超过模型限制
- 考虑分块处理长Prompt
- 选择合适的模型规格
7. 高级技巧与优化
7.1 多轮对话实现
要实现连贯的多轮对话,需要维护对话历史:
java复制List<Message> history = new ArrayList<>();
// 添加系统消息
history.add(new SystemMessage("你是一个专业的编程助手"));
// 第一轮
history.add(new UserMessage("如何实现单例模式?"));
ChatResponse response1 = chatModel.call(new Prompt(history));
history.add(response1.getResult().getOutput());
// 第二轮
history.add(new UserMessage("请用Kotlin实现"));
ChatResponse response2 = chatModel.call(new Prompt(history));
关键点:
- 保持SystemMessage在最前面
- 按顺序保存所有对话消息
- 注意控制对话历史长度,避免超过token限制
7.2 Prompt性能优化
对于高频调用的Prompt,可以考虑以下优化手段:
- 模板预编译:提前创建好PromptTemplate实例
- 缓存常用Prompt:对固定Prompt进行缓存
- 长度优化:精简不必要的文本内容
- 批量处理:合并相似请求减少调用次数
java复制// 预编译常用模板
private final PromptTemplate storyTemplate = new PromptTemplate(
"讲一个关于{topic}的{genre}故事,{length}字左右");
@GetMapping("/optimized-story")
public String optimizedStory(String topic) {
// 使用预编译模板
Prompt prompt = storyTemplate.create(Map.of(
"topic", topic,
"genre", "科幻",
"length", "300"
));
return chatModel.call(prompt).getResult().getOutput().getText();
}
8. 安全注意事项
在Prompt工程实践中,安全问题不容忽视:
-
Prompt注入防护
- 对用户输入进行严格过滤
- 在SystemMessage中明确安全限制
- 避免直接将用户输入拼接为Prompt
-
敏感信息保护
- 不在Prompt中包含机密数据
- 对输出内容进行安全审查
- 实现合适的访问控制
-
内容审核
- 对模型输出进行合规检查
- 建立关键词过滤机制
- 记录完整交互日志
一个安全的系统提示词示例:
code复制你是一个技术支持助手,必须遵守以下规则:
1. 不透露任何内部系统信息
2. 不执行任何代码或命令
3. 不讨论政治、宗教等敏感话题
4. 遇到可疑请求时回复:"抱歉,我无法协助这个请求"
在实际项目中,Prompt工程既是一门科学也是一门艺术。需要不断测试和迭代,才能找到最适合特定场景的Prompt设计。通过Spring AI提供的这套API,我们能够以符合Java工程实践的方式,构建出高效、可靠的大模型交互方案。
