Prompts
提示词(Prompts)是引导 AI 模型生成期望输出的输入内容。提示词的设计和措辞会显著影响模型的响应质量。
在 Spring AI 中,最底层的模型交互方式与 Spring MVC 中处理 “View(视图)” 的方式有些相似:你需要创建包含占位符的大段文本,然后根据用户请求或应用程序中的其他逻辑,将这些占位符替换为动态内容。另一个类比是带有占位符的 SQL 语句。
随着 Spring AI 的不断发展,将会引入更高层次的抽象来与 AI 模型交互。本章节介绍的基础类可类比为 JDBC 所扮演的角色。例如:
- ChatModel类 类似于 JDK 中的 JDBC 核心库,提供最底层的模型调用能力。
- ChatClient类 则类似于
JdbcClient,它构建在ChatModel之上,并结合Advisor提供更高级的功能,例如:- 考虑模型的历史对话内容
- 使用上下文文档增强提示词
- 引入具备一定 “代理(Agentic)行为” 的能力
在 AI 领域,提示词的结构发展经历了多个阶段:
- 最初:提示词只是简单的字符串。
- 之后:提示词发展为包含输入占位符,例如 “USER:” 等,让 AI 模型更易理解上下文角色。
- 再之后:OpenAI 等引入了更结构化的提示词格式,通过将多条消息区分为不同角色(如
system、user、assistant)再提供给模型,从而增强提示工程的表达力与清晰度。
API 概览
Prompt
通常情况下,会使用 ChatModel 的 call() 方法,它接收一个 Prompt 实例并返回一个 ChatResponse。
Prompt 类的作用是:作为一个容器,按顺序组织一系列 Message 对象,并附带请求的 ChatOptions。每个 Message 在提示词中都有其独特的 “角色(role)”、内容与意图,可能包括:
- 用户提问
- AI 的回答
- 背景知识
- 系统指令
- 任何额外的上下文信息
通过这些消息的组合与分工,可以构建更加复杂、层次分明的模型交互,形成多轮、结构化的提示词,而不仅仅是单一字符串输入。
下面是 Prompt 类的简化版本(为简洁起见,已省略构造函数与工具方法):
public class Prompt implements ModelRequest<List<Message>> {
private final List<Message> messages;
private ChatOptions chatOptions;
}
Message
Message 接口封装了 提示词文本内容(Prompt textual content)、一组元数据属性(metadata),以及一个称为 MessageType 的分类信息。
接口定义如下:
public interface Content {
String getContent();
Map<String, Object> getMetadata();
}
public interface Message extends Content {
MessageType getMessageType();
}
多模态(Multimodal)消息类型还会实现 MediaContent 接口,用于提供一组多媒体内容对象:
public interface MediaContent extends Content {
Collection<Media> getMedia();
}
Message 接口有多种实现类,它们代表 AI 模型能够处理的不同类型的消息。模型会根据 对话角色(conversational roles) 来区分这些消息类型。

这些角色由 MessageType 进行有效映射,具体说明如下。
角色(Roles)
每条消息都会被分配一个特定的 “角色”。这些角色用于对消息进行分类,从而让 AI 模型更清晰地理解每段提示内容的上下文和用途。这种结构化方式让与 AI 的交流更精确,因为提示的每一部分都承担着明确且独立的作用。
主要角色包括:
- System 角色:用于指导 AI 的行为和回复风格,设定 AI 在对话中应遵循的参数或规则。这类似于在开始对话前给 AI 下达 “使用说明” 或 “行为规范”。
- User 角色:表示用户的输入——即用户向 AI 提出的提问、命令或陈述。这是 AI 生成回答的核心依据。
- Assistant 角色:AI 针对用户输入做出的回应。该角色不仅仅是回答,更用于维持整个对话的连贯性。通过保留 AI 过去的 Assistant 消息,系统能够保持一致且上下文相关的交互。此外,Assistant 消息还可能 包含函数工具调用(Function Tool Call)信息。这是 AI 的一个特殊能力,用于执行额外任务,如计算、抓取数据等,而不仅仅是对话。
- Tool / Function 角色:该角色用于返回工具调用(Tool Call)后的结果,是对 Assistant 角色中工具调用请求的响应。
在 Spring AI 中角色以枚举类型表示:
public enum MessageType {
USER("user"),
ASSISTANT("assistant"),
SYSTEM("system"),
TOOL("tool");
...
}
PromptTemplate
在 Spring AI 中,提示词模板(prompt templating)的核心组件是 PromptTemplate 类。它用于构建结构化的 Prompt,并将其发送给 AI 模型进行处理。
public class PromptTemplate implements PromptTemplateActions, PromptTemplateMessageActions {
// 其他方法稍后介绍
}
该类依赖 TemplateRenderer API 来执行模板渲染。默认情况下,Spring AI 使用 StTemplateRenderer,它基于 Terence Parr 开发的开源 StringTemplate 引擎。模板变量默认使用 {} 语法标记,但你也可以自定义分隔符。
public interface TemplateRenderer extends BiFunction<String, Map<String, Object>, String> {
@Override
String apply(String template, Map<String, Object> variables);
}
Spring AI 使用 TemplateRenderer 接口来处理模板字符串中的变量替换。默认实现使用 StringTemplate,但你可以提供自己的实现来满足更复杂的逻辑需求。如果模板字符串已经是最终内容,不需要任何渲染,你可以使用 NoOpTemplateRenderer。
示例:使用自定义 StringTemplate 渲染器(自定义 < 和 > 作为分隔符)
PromptTemplate promptTemplate = PromptTemplate.builder()
.renderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
.template("""
Tell me the names of 5 movies whose soundtrack was composed by <composer>.
""")
.build();
String prompt = promptTemplate.render(Map.of("composer", "John Williams"));
该类所实现的接口分别支持提示构建过程中的不同方面:
PromptTemplateStringActions专注于创建和渲染提示字符串,是最基础的提示生成方式。PromptTemplateMessageActions用于通过生成和操作Message对象来构建提示,更贴合多消息、结构化对话的场景。PromptTemplateActions用于返回最终的Prompt对象,该对象可直接传递给ChatModel以生成模型响应。
虽然在多数项目中不会大量使用这些接口,但它们展示了多种不同的提示构建方式。
以下是这些已实现的接口:
1. PromptTemplateStringActions
用于创建和渲染纯文本形式的 Prompt,是最基础的提示词生成方式。
public interface PromptTemplateStringActions {
String render();
String render(Map<String, Object> model);
}
render():直接将模板渲染为最终字符串,不依赖外部输入,适用于无占位符的模板。render(Map<String, Object> model):将模板与动态数据结合,model的key为占位符名称,value为动态值。
2. PromptTemplateMessageActions
用于生成 Message 对象,适合构建多角色、多消息的对话式 Prompt。
public interface PromptTemplateMessageActions {
Message createMessage();
Message createMessage(List<Media> mediaList);
Message createMessage(Map<String, Object> model);
}
createMessage():创建无动态数据的静态Message。createMessage(List<Media> mediaList):创建带静态文本和媒体内容的Message。createMessage(Map<String, Object> model):带动态占位符替换的Message。
3. PromptTemplateActions
最终用于创建 Prompt 对象,可直接传给 ChatModel。
public interface PromptTemplateActions extends PromptTemplateStringActions {
Prompt create();
Prompt create(ChatOptions modelOptions);
Prompt create(Map<String, Object> model);
Prompt create(Map<String, Object> model, ChatOptions modelOptions);
}
create():生成无动态数据的Prompt。create(ChatOptions):生成无动态数据且带特定请求参数(如温度、top_p 等)的Prompt。create(Map<String, Object>):扩展提示生成能力以包含动态内容,使用一个Map<String, Object>,其中每个键值对对应提示模板中的一个占位符及其关联的动态值。create(Map<String, Object>, ChatOptions):扩展提示生成能力以包含动态内容,使用一个Map<String, Object>,其中每个键值对对应提示模板中的一个占位符及其关联的动态值,并包含针对聊天请求的特定选项。
这些接口虽然在许多项目中未必全都用得上,但它们展示了构建 Prompt 的不同方法和抽象层级,适合在需要高级 Prompt 控制的场景下使用。
示例用法
以下示例摘自 AI Workshop 中关于 PromptTemplates 的内容:
PromptTemplate promptTemplate = new PromptTemplate("Tell me a {adjective} joke about {topic}");
Prompt prompt = promptTemplate.create(Map.of("adjective", adjective, "topic", topic));
return chatModel.call(prompt).getResult();
另一个示例则来自 AI Workshop 中关于 Roles 的内容:
String userText = """
Tell me about three famous pirates from the Golden Age of Piracy and why they did.
Write at least a sentence for each pirate.
""";
Message userMessage = new UserMessage(userText);
String systemText = """
You are a helpful AI assistant that helps people find information.
Your name is {name}
You should reply to the user's request with your name and also in the style of a {voice}.
""";
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemText);
Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", name, "voice", voice));
Prompt prompt = new Prompt(List.of(userMessage, systemMessage));
List<Generation> response = chatModel.call(prompt).getResults();
这个示例展示了如何使用 SystemPromptTemplate 生成一个带有系统角色(system role)的 Message,并向其中注入占位符参数。然后再将用户角色(user role)的消息与系统角色的消息组合起来构建完整的 Prompt。最后,将该 Prompt 传递给 ChatModel,以获取生成式响应。
使用自定义模板渲染器
你可以通过自行实现 TemplateRenderer 接口,并将其传递给 PromptTemplate 的构造函数,来使用自定义的模板渲染器。你也可以继续使用默认的 StTemplateRenderer,但通过自定义配置来调整其行为。
默认情况下,模板变量使用 {} 语法标识。如果你打算在提示词中包含 JSON,为避免与 JSON 的 {} 产生冲突,可能需要使用其他分隔符。例如,你可以使用 < 和 > 作为占位符的分隔符。
PromptTemplate promptTemplate = PromptTemplate.builder()
.renderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
.template("""
Tell me the names of 5 movies whose soundtrack was composed by <composer>.
""")
.build();
String prompt = promptTemplate.render(Map.of("composer", "John Williams"));
使用资源文件而不是原始字符串
Spring AI 支持 org.springframework.core.io.Resource 抽象,因此你可以将提示词内容放在文件中,并直接作为 PromptTemplate 的数据来源。例如,你可以在 Spring 管理的组件中定义一个字段来注入该 Resource:
@Value("classpath:/prompts/system-message.st")
private Resource systemResource;
然后将这个资源直接传递给 SystemPromptTemplate:
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource);
提示工程
在生成式 AI 中,创建高质量的提示词(Prompt)是开发者的一项关键任务。提示词的质量和结构会显著影响 AI 输出的效果。投入时间和精力设计周到的提示词,可以大幅提升 AI 的生成结果。
在 AI 社区中,共享和讨论提示词 是一种常见做法。这种协作方式不仅能营造共享学习的环境,还能帮助发现和使用高效的提示词。
相关研究通常通过分析和比较不同的提示词来评估其在各种场景下的效果。例如,一项重要研究表明,以 “深呼吸,然后逐步解决这个问题” 作为提示开头,能够显著提升问题解决的效率。这说明,精心选择的语言对生成式 AI 系统的表现有着直接而明显的影响。
随着 AI 技术的快速发展,掌握提示词的最佳使用方式仍是一项持续的挑战。开发者应认识到提示词工程(Prompt Engineering)的重要性,并考虑借鉴社区经验和研究成果,以优化提示词的创建策略。
创建有效的提示词
在开发提示词(Prompt)时,整合若干关键组成部分对于确保提示的清晰性和有效性至关重要:
- 指令(Instructions):向 AI 提供清晰、直接的指令,就像与人交流一样。这种清晰性有助于 AI “理解” 预期的任务和输出。
- 外部上下文(External Context):在必要时提供相关背景信息或对 AI 输出的具体指导。这种 “外部上下文” 为提示提供框架,帮助 AI 理解整体情境。
- 用户输入(User Input):最直接的部分,即用户的请求或问题,构成提示的核心内容。
- 输出指示(Output Indicator):这部分可能较为复杂,涉及指定 AI 输出的期望格式,例如 JSON。但需注意,AI 并不总是严格遵守该格式。例如,它可能在 JSON 数据前加上 “here is your JSON” 这样的前缀,或者生成近似 JSON 但不完全准确的结构。
为 AI 提供预期的问答示例非常有帮助,这有助于 AI 理解查询的结构和意图,从而生成更精确、相关的响应。尽管本指南未深入探讨这些技术,但它们为进一步探索提示词工程提供了起点。
以下是可进一步研究的一些资源和技术:
基础技术(Simple Techniques)
- 文本摘要(Text Summarization):将大量文本压缩成简明摘要,抓住关键点和主要思想,同时省略次要内容。
- 问答(Question Answering):基于用户提出的问题,从文本中提取具体答案,精准定位所需信息。
- 文本分类(Text Classification):将文本系统化地归入预定义类别,通过分析内容分配最适合的类别。
- 对话(Conversation):创建交互式对话,让 AI 与用户进行自然的多轮交流。
- 代码生成(Code Generation):根据用户需求或描述生成可执行代码片段,将自然语言指令转换为功能性代码。
高级技术(Advanced Techniques)
- 零样本/少样本学习(Zero-shot, Few-shot Learning):使模型在几乎没有或仅有少量示例的情况下,仍能准确执行任务,利用已学的通用知识处理新问题。
- 链式思维(Chain-of-Thought):将多条 AI 响应串联起来,形成连贯且具上下文的对话,确保讨论的相关性和连续性。
- ReAct(Reason + Act):AI 先分析输入(推理),再决定最适当的行动或响应,将理解与决策相结合。
微软指导(Microsoft Guidance)
- 提示词创建与优化框架(Framework for Prompt Creation and Optimization):微软提供了一套结构化方法,用于开发和优化提示词。该框架指导用户创建能够有效引导 AI 模型生成所需响应的提示,提升交互的清晰度与效率。
Tokens(令牌)
在 AI 模型处理文本的过程中,Tokens(令牌) 至关重要,它们充当了桥梁,将我们理解的单词转换为 AI 模型能够处理的格式。这个转换分两个阶段进行:输入时,单词被转化为令牌;输出时,这些令牌再转换回单词。
分词(Tokenization) 是将文本拆分为令牌的过程,是 AI 模型理解和处理语言的基础。模型通过这种令牌化的格式来理解提示并生成响应。
为了更好地理解令牌,可以将其视为单词的一部分。通常,一个令牌大约占一个单词的四分之三。例如,莎士比亚的全集大约有 90 万个单词,对应大约 120 万个令牌。可以使用 OpenAI Tokenizer 的 UI 实验,查看单词是如何转换为令牌的。
令牌不仅在技术上用于 AI 处理,还有实际应用上的重要意义,尤其在计费和模型能力方面:
- 计费(Billing):AI 模型服务通常按令牌使用量计费。输入(Prompt)和输出(Response)都会计入总令牌数,因此较短的提示词更经济。
- 模型限制(Model Limits):不同 AI 模型的令牌上限不同,这定义了它们的 “上下文窗口”(context window)——即模型一次能够处理的最大信息量。例如,GPT-3 的上限为 4K 令牌,而 Claude 2 和 Meta LLaMA 2 的上限为 100K 令牌,一些研究模型甚至可以处理多达 100 万令牌。
- 上下文窗口(Context Window):模型的令牌上限决定了其上下文窗口。超过该限制的输入将无法被处理。因此,只需发送最小有效信息即可。例如,在查询《哈姆雷特》时,不必包含莎士比亚其他作品的所有令牌。
- 响应元数据(Response Metadata):AI 模型响应中包含的元数据会显示使用的令牌数量,这是管理使用量和成本的重要信息。