引言
在生成式 AI 应用中,Prompt(提示)是与大型语言模型(LLM)交互的核心输入格式。Prompt 的设计不仅决定了模型理解任务的准确度,还直接影响生成结果的风格、长度、结构与可控性。随着模型能力和应用场景的不断丰富,Prompt 的结构也从最初的简单文本演进为支持多角色区分、占位符模板化、工具调用等高级特性。本文将系统梳理 Prompt 的演变历程、角色(Role)分类及职责、Spring AI 中 Prompt API 的详细使用方法,以及 PromptTemplate 的模板化实践,并在每个小节提供详尽的文字说明和代码示例。
一、Prompt 的演变历程
Prompt 的演进大致可分为三个阶段:
1.1 初始阶段:简单字符串
背景与特点
-
实现方式:直接将用户意图用自然语言表述为一条字符串,传入模型。
-
示例:
Tell me a joke about cats.
-
优点:上手门槛低,无需额外框架或结构。
-
局限:无法提供上下文区分,也难以对输出进行精细控制,适用于单轮、简单的任务。
使用场景
- 问答类 Chatbot 的一次性查询
- 单条文本生成,如自动撰写短消息或标语
1.2 占位符与参数化
为什么要参数化?
- 当同一类任务需要多次调用时,硬编码不同 Prompt 会导致维护成本高。
- 参数化模板能够通过动态替换变量,提高 Prompt 的复用性和可维护性。
基本示例
String template = "Tell me a {adjective} joke about {topic}.";
String adjective = "silly";
String topic = "robots";
// 使用简单替换实现参数化
String prompt = template.replace("{adjective}", adjective).replace("{topic}", topic);
System.out.println(prompt);
// 输出:Tell me a silly joke about robots.
详细说明
- 模板定义:使用
{}
包裹的占位符表示可替换的变量位置。 - 动态赋值:在运行时根据业务逻辑提供不同的变量值。
- 字符串替换:示例中使用
String.replace
实现,适合简单场景,但无法处理复杂数据类型(如列表、条件逻辑)。
参数化模板的优势
- 高复用性:一份模板可支持多种输入组合。
- 易于维护:模板与参数分离,修改模板无需改动调用代码。
- 结构化:在模板中明确标识可变部分,增强可读性。
1.3 多角色消息流与上下文管理
角色化的必要性
随着对话式应用需求的增长,仅靠单条字符串难以维护多轮对话上下文,也无法区分系统指令与用户输入。引入角色(Role)后,每条消息都可携带角色标签,明确其在对话中的作用。
角色类型
- SYSTEM:设定对话基调、行为规范或全局约束。
- USER:真实用户的提问或指令。
- ASSISTANT:模型的回复,用于记录对话历史和保持连贯性。
- TOOL:外部工具或函数调用的输入/输出信息。
示例代码
List<Message> messages = new ArrayList<>();// 系统角色:初始化对话规则
messages.add(new SystemMessage("You are a professional travel planner. Provide concise and accurate travel advice."));// 用户角色:用户发起需求
messages.add(new UserMessage("I want to visit Xinjiang in autumn. What do you recommend?"));// 模型角色:占位符,由模型生成后再添加到列表
// messages.add(new AssistantMessage(modelOutput));// 下一轮用户角色:继续提问
// messages.add(new UserMessage("What is the weather forecast there for the next 3 days?"));
详细说明
- SystemMessage:通过
new SystemMessage(...)
创建,内容为对模型的全局指令。 - UserMessage:通过
new UserMessage(...)
创建,内容为用户输入。 - AssistantMessage:模型生成的回复,由程序接收并添加,用于多轮对话。
- Role 的作用:模型在生成回复时会根据角色区分系统指令与用户对话,确保响应符合预期。
二、Prompt 中的角色(Role)详解
在 Spring AI 中,MessageType
枚举将角色明确定义为四种类型:
public enum MessageType {SYSTEM("system"), // 系统指令USER("user"), // 用户输入ASSISTANT("assistant"), // 模型回复TOOL("tool"); // 工具调用
}
2.1 系统角色(System Role)
-
主要职责:向模型提供身份设定、业务规则或回答风格。
-
典型指令:
- “You are a helpful assistant that speaks formally.”
- “Only answer questions related to legal advice.”
代码示例
Message system = new SystemMessage("You are a financial advisor. Provide concise and accurate investment suggestions in JSON format."
);
扩展说明
- 可以通过添加元数据(metadata)为系统消息携带额外信息,如时间戳或优先级。
- 多个
SystemMessage
可按顺序添加,以分阶段设定对话规则。
2.2 用户角色(User Role)
- 主要职责:传达用户的具体需求或问题。
- 设计要点:用户消息应清晰、简洁,避免含糊不清的描述。
代码示例
Message user = new UserMessage("Please recommend three budget-friendly hotels in Tokyo for a family of four."
);
扩展说明
- 可在用户消息中列出多步操作,模型会尝试按顺序执行。
- 对于复杂查询,建议在一条消息中明确所有必要参数,如时间、人数、预算等。
2.3 助手角色(Assistant Role)
- 主要职责:承载模型的回复内容,同时可触发工具调用。
- 触发工具调用:当模型需要执行计算或外部操作时,会在
AssistantMessage
中返回function_call
字段,表明后续需调用对应工具。
代码示例
// 模型生成的回复示例
Message assistant = new AssistantMessage("The weather in Xinjiang for the next 3 days is:\nDay1: 18°C, sunny\nDay2: 20°C, partly cloudy\nDay3: 17°C, light rain"
);
扩展说明
- 在接收到
AssistantMessage
后,程序应检查是否存在工具调用请求,并根据tool_name
和arguments
调用相应工具。 - 回复中可包含结构化内容(如 JSON、表格等),便于后续解析。
2.4 工具/功能角色(Tool/Function Role)
- 主要职责:传递外部工具执行结果,如 API 调用、数据库查询或数学运算。
- 示例场景:天气查询、汇率转换、订单查询等。
代码示例
// 工具调用返回示例
Message tool = new ToolMessage("{ \"location\": \"Xinjiang\", \"forecast\": [18,20,17] }"
);
扩展说明
ToolMessage
的content
通常为 JSON 格式,程序解析后将结果注入下一次 Prompt。- 多个工具调用可串联使用,形成复杂工作流。
三、Spring AI 的 Prompt API 深入
Spring AI 提供了 Prompt
和 Message
等核心类,简化 Prompt 构建与调用流程。以下示例演示完整调用链路,并对关键方法进行详细解读。
3.1 构建 Prompt 实例
// 1. 导入依赖:确保已引入 spring-ai 相关库// 2. 构建消息列表
List<Message> messages = new ArrayList<>();
messages.add(system);
messages.add(user);// 3. 创建 Prompt 对象
Prompt prompt = new Prompt(messages)// 4. 可选:设置 ChatOptions,如温度、最大 Token.options(ChatOptions.builder().temperature(0.5).maxTokens(500).build());
详细说明
- 消息列表初始化:使用
ArrayList
存储各角色消息,顺序决定 Prompt 的上下文顺序。 - Prompt 构造函数:
new Prompt(List<Message>)
将消息列表复制到内部。 - 链式调用:通过
options(...)
方法设置模型参数,如温度(temperature)控制随机性,maxTokens 控制最大生成长度。
3.2 调用 ChatModel
// 5. 使用 ChatModel 或 ChatClient 发起调用
ChatResponse response = chatModel.call(prompt);// 6. 解析响应
String reply = response.getResult().getOutput().getContent();
System.out.println("Assistant: " + reply);
详细说明
- ChatModel.call():接收
Prompt
,发送给底层 LLM 服务,并返回ChatResponse
。 - ChatResponse:封装模型生成的所有输出,包括文本、工具调用请求、Usage 信息等。
- getResult().getOutput().getContent():提取生成文本内容。
3.3 动态添加消息
在多轮对话中,可根据上一轮回复动态添加消息:
// 上一轮回复添加为 AssistantMessage
messages.add(new AssistantMessage(reply));// 下一轮用户输入
messages.add(new UserMessage("Thank you. Can you also suggest local cuisines?"));// 重新构建并调用 Prompt
Prompt nextPrompt = new Prompt(messages);
ChatResponse nextResp = chatModel.call(nextPrompt);
详细说明
- 将模型回复添加到消息列表,保证上下文连贯性。
- 新的用户消息与历史消息一起传入,模型能够参考整个对话历史。
四、PromptTemplate:高级模板化设计
当 Prompt 逻辑复杂、参数众多时,手动拼接字符串或管理消息列表容易出错。PromptTemplate
基于 StringTemplate 引擎提供了灵活的模板化方案。
public class PromptTemplate implements PromptTemplateActions, PromptTemplateMessageActions {private final String template;public PromptTemplate(String template) {this.template = template;}// render/create 方法由接口提供,具体由 StringTemplate 引擎实现
}
4.1 渲染为纯文本
// 无参数模板
PromptTemplate staticTpl = new PromptTemplate("List three facts about the moon.");
String staticPrompt = staticTpl.render();
// staticPrompt == "List three facts about the moon."// 带参数模板
PromptTemplate paramTpl = new PromptTemplate("List three {adjective} facts about the {subject}.");
String dynamicPrompt = paramTpl.render(Map.of("adjective", "interesting","subject", "moon"
));
// dynamicPrompt == "List three interesting facts about the moon."
详细说明
- render():直接输出模板内容,适用于静态 Prompt。
- render(Map):根据键值对替换占位符,适用于简单参数化需求。
4.2 生成 Message 对象
// 仅文本消息
Message msg1 = paramTpl.createMessage(Map.of("adjective", "funny","subject", "penguins"
));
// msg1.getContent() -> "List three funny facts about the penguins."
// 默认角色为 USER,可通过重载指定其他角色
详细说明
- createMessage(Map):将渲染后的文本封装为
UserMessage
,简化消息构建流程。 - 也可使用重载方法传入媒体列表(图片、音频等),扩展多模态能力。
4.3 生成完整 Prompt
// 创建带系统和用户消息的模板
String tpl = "<system>You are {role}.</system>" +"<user>Summarize the following text: {text}</user>";
PromptTemplate fullTpl = new PromptTemplate(tpl);Map<String,Object> data = Map.of("role", "an academic writer","text", "Artificial intelligence is transforming industries..."
);// 生成 Prompt 并设置选项
Prompt fullPrompt = fullTpl.create(data).options(ChatOptions.builder().maxTokens(200).build());ChatResponse fullResp = chatModel.call(fullPrompt);
System.out.println(fullResp.getResult().getOutput().getContent());
详细说明
- 模板标签:使用
<system>
、<user>
标签将渲染内容分配给不同角色。 - create(Map):渲染并拆分消息为多条
Message
,并封装为Prompt
。 - options(...):为该 Prompt 单独设置模型参数。
五、进阶用法与最佳实践
-
分层组织 Prompt:
- 将不同职责的消息(系统、用户、助手、工具)分层组织,保持清晰的调用链路。
- 例如,先添加所有系统级指令,再添加用户级查询,最后再执行工具调用。
-
语义化占位符:
- 使用有意义的占位符名称,如
{userName}
、{startDate}
,提高模板可读性和可维护性。
- 使用有意义的占位符名称,如
-
模板管理:
- 将常用 PromptTemplate 存储在配置文件或数据库中,支持在线更新和版本控制。
- 结合 CI/CD 流程,实现模板自动化测试与回滚。
-
工具调用预留:
- 在模板中预先定义工具调用点,如
Please fetch current weather via function_call.
- 模型在生成时会自动触发
function_call
,程序接收后执行相应工具。
- 在模板中预先定义工具调用点,如
-
性能与成本控制:
- 缓存静态 Prompt 或已渲染模板,减少重复渲染开销。
- 控制消息列表长度,定期对历史对话进行摘要或清理,避免超出模型上下文窗口。
-
安全与合规:
- 对用户输入进行校验和清洗,避免注入恶意指令。
- 记录 Prompt 与响应日志,确保可审计、可追溯。
六、总结
Prompt 设计是生成式 AI 应用的基础,直接影响模型的行为和输出质量。通过理解 Prompt 的演变历程、合理划分角色、熟练使用 Spring AI 提供的 Prompt API 与 PromptTemplate 工具,开发者可以构建出灵活、高效且易维护的对话系统。结合最佳实践,您将能够在各类业务场景中充分发挥 LLM 的潜力,为用户带来流畅、智能的交互体验。
本文示例基于 Spring AI 框架及 StringTemplate 引擎,旨在为开发者提供系统的 Prompt 设计指导。