SpringAI实践(3) - 场景定义、角色管理与结构化输出
1.前言
在使用SpringAI构建智能应用时,如何精准地控制大模型的行为、输入输出格式以及上下文理解能力,是开发者面临的核心挑战。本文将深入探讨SpringAI框架中的消息类型机制、提示词模板的高级用法以及如何实现结构化数据输出。通过掌握这些技术,开发者可以构建更加可控、专业且符合业务需求的AI应用。
1.消息类型深度解析
在SpringAI框架的交互模型中,Message接口是构建对话的基础。最常用的两种实现类是UserMessage和SystemMessage。理解它们的区别对于精准控制AI模型的行为至关重要。
1.1.核心差异对比
| 维度 | UserMessage | SystemMessage |
|---|---|---|
| 角色定义 | 代表终端用户 | 代表系统或开发者 |
| 主要用途 | 承载用户的查询、指令或对话内容 | 设定AI的角色、行为准则、输出规范及对话背景 |
| 内容性质 | 具体的业务数据、自然语言提问 | 元信息,如“你是一个资深的Java开发专家”或“请用JSON格式输出” |
| 处理优先级 | 在系统设定的约束框架内被处理 | 高优先级。模型会优先遵循SystemMessage的指令,以此作为生成回复的基础约束 |
| 典型场景 | 日常问答、任务触发、信息检索 | 人设设定(Persona)、领域知识限定、风格微调、安全护栏设置 |
1.2.代码实现与逻辑
在代码层面,SpringAI提供了清晰的API来创建这两种消息。以下示例展示了如何分别构建并发送用户消息和系统消息:
public String userMessage(String message) {
Message promptMessage = new UserMessage(message);
return openAiChatClient.prompt(new Prompt(promptMessage)).call().content();
}
public String systemMessage(String message) {
Message promptMessage = new SystemMessage(message);
return openAiChatClient.prompt(new Prompt(promptMessage)).call().content();
}
1.3.请求日志分析
为了更直观地理解两者的区别,我们可以观察发送至大模型API的底层请求日志。
UserMessage请求日志示例:
通常日志中会显示"user",其后紧跟具体的用户输入文本。这表示模型将其视为对话中的用户轮次。
SystemMessage请求日志示例:
日志中会明确标记"system"。模型在处理后续的用户消息时,会先读取该字段中的指令,以此调整生成的概率分布,确保回复符合系统设定的要求。

2.提示词模板与资源外部化
在企业级应用开发中,将提示词硬编码在Java代码中是一种反模式。这不仅降低了代码的可维护性,也使得非技术人员(如产品经理)难以调整提示词策略。SpringAI支持类似于Mustache或SpringExpressionLanguage(SpEL)的模板机制,允许我们将提示词定义在外部文件中。
2.1.资源文件准备
首先,在项目的resources目录下创建提示词模板文件和参考文档。
文档路径:src/main/resources/docs/document.md
此文件用于存放作为上下文的参考知识,例如产品手册或技术规范。
模板路径:src/main/resources/prompts/prompt.st
使用以下格式来回答问题。
如果你不知道答案,就说不知道,不要尝试自己编一个答案。
{context}
问题: {question}
有效的答案:
2.2.代码实现:动态填充与文档注入
下面的PromptService类演示了如何加载资源、动态填充模板,并根据业务逻辑决定是否注入外部文档上下文。
public class PromptService {
@Value("classpath:/docs/document.md")
private Resource promptDocResource;
@Value("classpath:/prompts/prompt.st")
private Resource promptQuestResource;
public String completion(String message, boolean stuffit) {
PromptTemplate promptTemplate = new PromptTemplate(promptQuestResource);
Map<String, Object> map = new HashMap<>();
map.put("question", message);
// 是否填充 prompt 上下文,以增强大模型回答。
if (stuffit) {
map.put("context", promptDocResource);
} else {
map.put("context", "");
}
return openAiChatClient
.prompt(promptTemplate.create(map))
.call().content();
}
}
2.3.使用说明
通过调用completion方法并设置stuffit参数为true,开发者可以轻松实现基于特定知识库的问答。这种方式有效解决了大模型知识滞后和幻觉问题。
3.结构化输出解析
LLM默认输出的是非结构化文本。但在后端开发中,我们通常需要将提取到的信息直接映射为Java对象(POJO)以便于业务处理。SpringAI提供了便捷的.entity()方法,支持将模型输出自动反序列化为指定类型的对象。
3.1.输出基本类型:布尔值判断
以下示例展示了如何判断用户输入是否包含投诉意图。这种方法常用于客服系统的工单自动分类或情绪预警。
public Boolean isComplaint(String message) {
Boolean content = openAiChatClient.prompt()
.system("""
请判断用户信息是否表达了投诉意图?
只能用 true 或 false 回答,不要输出多余内容
""")
.user(message)
.call()
.entity(Boolean.class);
return content;
}
3.2.输出复杂对象:信息提取
在处理表单自动填充或订单处理等场景时,从自然语言中提取结构化信息(如地址)非常有用。
首先定义一个标准的POJO类,字段命名应清晰且符合业务语义。
@Data
public class Address {
private String name;
private String phone;
private String province;
private String city;
private String district;
private String detail;
}
利用SpringAI的实体提取功能,将非结构化的地址字符串转换为Address对象。
public Address getAddress(String message) {
Address content = openAiChatClient.prompt()
.system("""
请从内容中提取收货信息
""")
.user(message)
.call()
.entity(Address.class);
return content;
}
编写单元测试验证提取的准确性。
@Test
void getAddress() {
Address address = structureService.getAddress("收货人:张三,电话188xxxxxxxx,地址:湖南省长沙市xx区xx栋xx单元xx号");
log.info("address:{}", address);
}
通过结构化输出,我们无需编写复杂的正则表达式或解析规则,即可利用LLM强大的语义理解能力,将杂乱的文本转化为标准化的业务数据,极大降低了开发成本并提高了系统的灵活性。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)