Spring AI 核心探秘:四大 Prompt 角色底层设计与完整闭环实战

摘要

Spring AI 通过消息(Message)抽象统一了与大模型的对话载体,将系统指令、用户输入、助手回复和工具调用结果分别封装为四个独立的消息类。本文深入解析 SystemMessageUserMessageAssistantMessageToolResponseMessage 的底层设计、作用边界与配合方式,并结合 Spring AI Alibaba DashScope 给出从环境配置到多轮对话、Function Calling 完整闭环的代码实践,助你彻底吃透 Spring AI 的提示词工程核心。

1. 四大角色整体设计

Spring AI 将与模型的一次完整交互抽象为 Message 接口,并针对对话中不同来源和用途的内容设计了四个具体的实现类。每条消息都携带一个专属的 Role 标识,大模型通过这一角色区分语义边界,从而彻底告别混乱的字符串拼接。

角色类 枚举类型 核心作用 使用位置与特性
SystemMessage SYSTEM 全局系统指令,定义 AI 身份、输出规范、行为约束 消息列表首位,全局永久生效,不被用户覆盖
UserMessage USER 用户原始提问、业务输入、多模态内容 对话输入源,动态可变业务数据存放处
AssistantMessage ASSISTANT 大模型历史回答、工具调用请求载体 多轮上下文核心,可携带 ToolCall 指令
ToolResponseMessage TOOL 外部工具执行结果 Function Calling 闭环必备,补齐数据后回传

这四个角色的有序组合构成了 Prompt(提示词),最终交给模型推理。下面逐一拆解每个角色的设计意图和正确使用方式。

2. 角色详解与使用规范

2.1 SystemMessage ------ 全局指挥官

SystemMessage 是对话中最高优先级的指令,用于固定 AI 的人设、输出格式、领域约束和安全边界。在一轮完整对话的消息列表中,通常只在最前面放置一条。

⚠️ 使用禁忌

不要把动态变化的业务数据(如用户 ID、订单详情)放入 SystemMessage,因为每次变更都会导致提示词整体发生变化,白白消耗 token。系统消息应该保持稳定。

复制代码
SystemMessage systemMsg = new SystemMessage("""
    你是资深Java后端技术专家,基于Spring AI Alibaba框架解答问题;
    1. 回答必须附带可运行完整代码;
    2. 代码使用Java17规范,适配DashScope通义千问;
    3. 不编造不存在的框架API,解释简洁易懂。
    """);

2.2 UserMessage ------ 人类输入载体

所有来自用户侧的内容都通过 UserMessage 封装。它支持纯文本、图片(多模态)等输入,是触发每一次对话的源头。在多轮对话中,每一个新的用户提问都会创建一个新的 UserMessage 追加到消息列表末尾。

复制代码
// 普通文本提问
UserMessage userMsg = new UserMessage("讲解Spring AI Alibaba四大Prompt角色用法");

// 多模态示例(图片+文本)
// UserMessage multiModalMsg = new UserMessage("分析这张图片", 
//         List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageResource)));

2.3 AssistantMessage ------ 模型双向载体

AssistantMessage 身兼两职:

  1. 普通对话 :存储模型上一轮的回复内容。在构建多轮对话 Prompt 时,需要按顺序将历史 AssistantMessage 和后续 UserMessage 组合在一起,完整还原对话上下文。
  2. Agent 工具调用 :当模型判断需要调用外部工具时,它的回复会以 AssistantMessage 的形式返回,但其中包含 List<ToolCall> 对象,指明要调用的函数名和参数。

多轮上下文拼接示例

复制代码
[SystemMessage, UserMessage1, AssistantMessage1, UserMessage2, AssistantMessage2, ...]

// 普通回答
AssistantMessage assistant = new AssistantMessage("ChatModel是底层标准接口,ChatClient是上层便捷封装。");

// 工具调用(框架自动生成)
// AssistantMessage 内含 toolCalls 字段

2.4 ToolResponseMessage ------ 外部数据桥梁

仅在 Function Calling 场景出现。一条完整的工具调用链路为:

复制代码
UserMessage → AssistantMessage(含 ToolCall) → 本地执行工具 → ToolResponseMessage → 二次请求模型

ToolResponseMessage 将工具执行的结果封装,并将其与之前所有的消息(系统、用户、助手工具调用)合并,再次发送给大模型,由模型结合工具数据生成最终的自然语言回复。缺少该角色,就无法实现联网查询、数据库检索等 Agent 能力。

3. 项目环境配置

3.1 Maven 依赖

复制代码
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
    <version>1.0.0-M6.1</version>
</dependency>

3.2 application.yml 通用配置

复制代码
spring:
  ai:
    dashscope:
      api-key: sk-你的灵积API密钥
      chat:
        options:
          model: qwen-turbo
          temperature: 0.7

3.3 两套接入方案

Spring AI Alibaba 提供自动装配手动配置两种方式,推荐优先使用自动装配。

方案一:Starter 自动装配(90% 场景首选)

只需引入 starter 并配置 yml,DashScopeAutoConfiguration 会自动创建:

  • DashScopeApi(API 客户端)
  • DashScopeChatModelChatModel 接口实现)
  • 业务代码直接注入 ChatModelChatClient 即可。

无需任何配置类。

方案二:手动自定义配置类(需要定制网络参数时)

当需要自定义连接超时、HTTP 代理或私有化地址时,可以手动创建 Bean(此时自动装配会失效),并复用 DashScopeProperties 避免硬编码。

复制代码
@Configuration
public class DashScopeLLMConfig {

    @Bean
    public DashScopeApi dashScopeApi(DashScopeProperties properties) {
        return DashScopeApi.builder()
                .apiKey(properties.getApiKey())
                .connectTimeout(Duration.ofSeconds(30))
                .readTimeout(Duration.ofSeconds(60))
                // .baseUrl("私有化地址")
                // .proxy(自定义代理)
                .build();
    }

    @Bean
    public ChatModel chatModel(DashScopeApi api, DashScopeProperties properties) {
        return new DashScopeChatModel(api, properties.getChat().getOptions());
    }

    // 注册全局 ChatClient,统一管理系统提示词
    @Bean
    public ChatClient chatClient(ChatModel chatModel) {
        return ChatClient.builder(chatModel)
                .defaultSystem("你是Spring AI Alibaba技术专家,代码示例完整规范")
                .defaultTemperature(0.7)
                .build();
    }
}

4. 实战一:基础多轮对话(System + User + Assistant)

本案例演示如何手动组装三种角色消息,并对比 ChatModel 底层 API 和 ChatClient 高层封装两种调用方式。

复制代码
@RestController
public class PromptRoleController {

    private final ChatModel chatModel;
    private final ChatClient chatClient;

    public PromptRoleController(ChatModel chatModel, ChatClient chatClient) {
        this.chatModel = chatModel;
        this.chatClient = chatClient;
    }

    // 方式一:原生 ChatModel 手动构建多轮对话
    @GetMapping("/chat/multi/sync")
    public String multiRoundSync() {
        SystemMessage systemMsg = new SystemMessage("你是Java AI开发博主,回答简短精炼");
        UserMessage user1 = new UserMessage("什么是ChatModel与ChatClient区别?");
        AssistantMessage assistant1 = new AssistantMessage(
                "ChatModel是底层标准接口,ChatClient是上层便捷封装。");
        UserMessage user2 = new UserMessage("开发中该优先使用哪一个?");

        Prompt prompt = new Prompt(List.of(systemMsg, user1, assistant1, user2));
        return chatModel.call(prompt).getResult().getOutput().getText();
    }

    // 方式二:ChatClient 流式调用(推荐)
    @GetMapping("/chat/client/stream")
    public Flux<String> clientStreamChat(@RequestParam String query) {
        return chatClient.prompt()
                .system("讲解代码附带完整示例")
                .user(query)
                .stream()
                .content();
    }
}
  • ChatModel 需要手动维护消息列表顺序,适合底层精细控制。
  • ChatClient 内部自动管理上下文并支持链式 API,更贴近业务开发。

5. 实战二:四大角色全闭环 Function Calling

下面代码完整展示了 System、User、Assistant(带 ToolCall)和 ToolResponse 四个角色的协作过程,实现"查询天气"的 Agent 功能。

复制代码
@GetMapping("/chat/tool/allRole")
public String toolCallAllRole() throws Exception {
    // 1. 系统消息:约束必须使用工具
    SystemMessage systemMsg = new SystemMessage("""
        你拥有天气查询工具getCityWeather,需要查询天气时必须调用该工具,
        禁止编造数据。调用时传入参数city(城市名称)。
        """);
    // 2. 用户消息
    UserMessage userMsg = new UserMessage("查询北京今日气温");

    Prompt prompt = new Prompt(List.of(systemMsg, userMsg));
    ChatResponse response = chatModel.call(prompt);

    // 3. 获取助手消息(可能包含工具调用)
    AssistantMessage assistantMsg = response.getResult().getOutput();

    if (!assistantMsg.getToolCalls().isEmpty()) {
        ToolCall toolCall = assistantMsg.getToolCalls().get(0);
        // 解析参数
        JsonNode args = new ObjectMapper().readTree(toolCall.arguments());
        String city = args.get("city").asText();

        // 执行本地工具(模拟)
        String weatherData = getCityWeather(city);

        // 4. 构造 ToolResponseMessage
        ToolResponseMessage toolMsg = new ToolResponseMessage(
                List.of(new ToolResponse(toolCall.id(), weatherData))
        );

        // 5. 拼接完整消息二次请求
        List<Message> fullMessages = List.of(systemMsg, userMsg, assistantMsg, toolMsg);
        ChatResponse finalResp = chatModel.call(new Prompt(fullMessages));
        return finalResp.getResult().getOutput().getText();
    }

    return assistantMsg.getText();
}

private String getCityWeather(String city) {
    return String.format("%s今日:晴,气温18~28℃,微风", city);
}

关键步骤解析

  • 模型返回的 AssistantMessagegetToolCalls() 非空,说明需要调用外部工具;
  • 程序根据 ToolCall 中的函数名和参数执行本地逻辑,并将结果封装为 ToolResponseMessage
  • 第二次请求时消息列表必须为 [System, User, Assistant(含ToolCall), ToolResponse] 的顺序;
  • 模型结合工具数据生成最终的自然语言回答。

6. 总结与最佳实践

  1. 职责分离
    SystemMessage 定义稳态约束,UserMessage 承载动态输入,AssistantMessage 维护历史与工具指令,ToolResponseMessage 补全外部数据。各司其职,避免混合。
  2. 多轮上下文维护
    按顺序记录 System → User → Assistant → User → Assistant ...,切勿遗漏历史助手消息,否则模型会丢失对话记忆。
  3. 工具调用闭环
    当收到含有 ToolCallAssistantMessage 时,必须执行工具并附加 ToolResponseMessage 重新请求,否则对话会中断或产生幻觉。
  4. 优先使用 ChatClient
    日常开发推荐 ChatClient 的内置上下文管理、流式调用和默认系统提示词功能;仅在需要极端控制消息列表时下沉到 ChatModel
  5. 配置选择
    无特殊网络需求时用自动装配;需要自定义超时、代理或私有化地址时手动创建 Bean,同时复用 DashScopeProperties 保持配置统一。

Spring AI 的四大角色设计不仅使得提示词构建更加模块化和类型安全,也为后续的 Agent 编排、记忆管理打下了坚实的基础。理解并善用这些角色,你将能够构建出更加稳定、智能的 AI 应用。

相关推荐
一次旅行1 小时前
OpenAI 新版提示词指南
人工智能·chatgpt·github
hans汉斯1 小时前
人工智能与机器人研究|面向无标签数据的三维场景语义理解方法研究
人工智能·神经网络·算法·信息可视化·cnn·机器人
'pi%'1 小时前
“咒语”质量如何量化?大模型Prompt效果评估与自动化验证方案
运维·自动化·prompt
Web3_Daisy1 小时前
Robinhood Chain Launchpad:链上资产发行进入新阶段
大数据·人工智能·区块链
weixin_495248401 小时前
带硬字幕的老视频也能出海:短剧出海翻译服务商如何擦除重制?
人工智能·音视频
鲲穹AI种草1 小时前
演示文稿制作工具记录:多款 PPT 工具能力边界整理
人工智能·powerpoint·演示文稿制作工具
leoZ2319 小时前
本地跑大模型实战(七):llama.cpp 性能调优,让推理更快更省
java·人工智能·spring·生成对抗网络·语言模型·自然语言处理·llama
To_OC9 小时前
我把《天龙八部》塞进向量数据库后,终于搞懂了 RAG 到底是个啥
人工智能·llm·agent