07_Spring AI 干货笔记之提示词
本文详细介绍了Spring AI中的提示词核心概念与API设计。提示词作为引导AI模型生成特定输出的关键输入,其结构从简单字符串演进为包含多角色消息的复杂形式。Spring AI通过Prompt和Message接口提供结构化提示词管理,支持系统、用户、助手等角色分配。PromptTemplate类实现动态内容渲染,并支持自定义模板引擎。文章还涵盖提示词工程的最佳实践与令牌机制,为开发者提供完整的提
一、提示词
提示词是引导 AI 模型生成特定输出的输入内容。这些提示词的设计和措辞会显著影响模型的响应。
在 Spring AI 中与 AI 模型进行最低层级的交互时,处理提示词的方式有些类似于 Spring MVC 中管理"视图"。这涉及创建包含动态内容占位符的大量文本。然后根据用户请求或应用程序中的其他代码替换这些占位符。另一个类比是包含特定表达式占位符的 SQL 语句。
随着 Spring AI 的发展,它将引入更高级别的抽象来与 AI 模型交互。本节描述的基础类可以在其角色和功能上比作 JDBC。例如,ChatModel 类类似于 JDK 中的核心 JDBC 库。ChatClient 类可以比作构建在 ChatModel 之上的 JdbcClient,通过 Advisor 提供更高级的构造,以考虑与模型的过往交互、使用额外的上下文文档增强提示词,并引入智能体行为。
在 AI 领域,提示词的结构随着时间的推移而演变。最初,提示词是简单的字符串。后来,它们发展到包含特定输入的占位符,例如 AI 模型能够识别的 “USER:”。OpenAI 通过将多个消息字符串在由 AI 模型处理之前分类为不同的角色,为提示词引入了更多结构。
二、API 概述
2.1 Prompt
通常使用 ChatModel 的 call() 方法,该方法接受一个 Prompt 实例并返回一个 ChatResponse。
Prompt 类充当一个有组织的 Message 对象序列和请求 ChatOptions 的容器。每个 Message 在提示词中体现一个独特的角色,其内容和意图各不相同。这些角色可以包含多种元素,从用户查询到 AI 生成的响应,再到相关的背景信息。这种安排使得与 AI 模型的交互变得复杂而详细,因为提示词由多个消息构成,每个消息在对话中被分配一个特定的角色。
以下是 Prompt 类的简化版本,为简洁起见省略了构造函数和工具方法:
public class Prompt implements ModelRequest<List<Message>> {
private final List<Message> messages;
private ChatOptions chatOptions;
}
2.2 Message
Message 接口封装了提示词的文本内容、元数据属性集合以及称为 MessageType 的分类。
接口定义如下:
public interface Content {
String getContent();
Map<String, Object> getMetadata();
}
public interface Message extends Content {
MessageType getMessageType();
}
多模态消息类型也实现了 MediaContent 接口,提供了一个 Media 内容对象列表。
public interface MediaContent extends Content {
Collection<Media> getMedia();
}
Message 接口的各种实现对应于 AI 模型可以处理的不同消息类别。模型根据对话角色区分消息类别。
这些角色通过 MessageType 有效地映射,如下所述。
2.2.1 角色
每条消息都被分配一个特定的角色。这些角色对消息进行分类,向 AI 模型阐明提示词每个部分的上下文和目的。这种结构化的方法增强了与 AI 通信的细微差别和有效性,因为提示词的每个部分在交互中都扮演着独特且定义明确的角色。
主要角色有:
-
系统角色: 指导 AI 的行为和响应风格,为 AI 解释和回复输入设置参数或规则。这类似于在开始对话前向 AI 提供指令。
-
用户角色: 代表用户的输入——他们向 AI 提出的问题、命令或陈述。这个角色是基础的,因为它构成了 AI 响应的基础。
-
助手角色: AI 对用户输入的响应。它不仅仅是一个答案或反应,对于维持对话的流畅性至关重要。通过跟踪 AI 之前的响应(其"助手角色"消息),系统确保交互具有连贯性和上下文相关性。助手消息也可能包含函数工具调用请求信息。这就像是 AI 中的一个特殊功能,在需要时用于执行特定功能,例如计算、获取数据或除对话之外的其他任务。
-
工具/函数角色: 工具/函数角色侧重于返回附加信息以响应工具调用助手消息。
角色在 Spring AI 中以下列枚举形式表示:
public enum MessageType {
USER("user"),
ASSISTANT("assistant"),
SYSTEM("system"),
TOOL("tool");
...
}
2.3 PromptTemplate
Spring AI 中提示词模板化的一个关键组件是 PromptTemplate 类,旨在促进创建结构化提示词,然后将其发送给 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]。如果您需要自定义逻辑,可以提供自己的 TemplateRenderer 实现。对于不需要模板渲染的场景(例如,模板字符串已经完整),您可以使用提供的 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 以生成响应。
虽然这些接口在许多项目中可能不会广泛使用,但它们展示了提示词创建的不同方法。
实现的接口是:
public interface PromptTemplateStringActions {
String render();
String render(Map<String, Object> model);
}
-
String render() 方法:在没有外部输入的情况下将提示词模板渲染成最终的字符串格式,适用于没有占位符或动态内容的模板。
-
String render(Map<String, Object> model) 方法:增强渲染功能以包含动态内容。它使用一个 Map<String, Object>,其中 Map 的键是提示词模板中的占位符名称,值是要插入的动态内容。
public interface PromptTemplateMessageActions {
Message createMessage();
Message createMessage(List<Media> mediaList);
Message createMessage(Map<String, Object> model);
}
-
Message createMessage() 方法:在没有额外数据的情况下创建一个 Message 对象,用于静态或预定义的消息内容。
-
Message createMessage(List mediaList) 方法:创建一个包含静态文本和媒体内容的 Message 对象。
-
Message createMessage(Map<String, Object> model) 方法:扩展消息创建功能以集成动态内容,接受一个 Map<String, Object>,其中每个条目代表消息模板中的一个占位符及其对应的动态值。
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);
}
-
Prompt create() 方法:在没有外部数据输入的情况下生成一个 Prompt 对象,适用于静态或预定义的提示词。
-
Prompt create(ChatOptions modelOptions) 方法:在没有外部数据输入的情况下生成一个 Prompt 对象,并为聊天请求指定特定选项。
-
Prompt create(Map<String, Object> model) 方法:扩展提示词创建能力以包含动态内容,接受一个 Map<String, Object>,其中每个 Map 条目是提示词模板中的一个占位符及其关联的动态值。
-
Prompt create(Map<String, Object> model, ChatOptions modelOptions) 方法:扩展提示词创建能力以包含动态内容,接受一个 Map<String, Object>,其中每个 Map 条目是提示词模板中的一个占位符及其关联的动态值,并为聊天请求指定特定选项。
三、使用示例
下面展示一个来自 AI Workshop on 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 on 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 创建一个具有系统角色的 Message 并传入占位符值,然后将具有用户角色的消息与具有系统角色的消息组合起来形成提示词。接着将提示词传递给 ChatModel 以获取生成式响应。
3.1 使用自定义模板渲染器
您可以通过实现 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"));
3.2 使用资源代替原始字符串
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 中,创建提示词是开发人员的一项关键任务。这些提示词的质量和结构显著影响 AI 输出的有效性。投入时间和精力设计周到的提示词可以大大改善 AI 的结果。
在 AI 社区中,分享和讨论提示词是一种常见做法。这种协作方法不仅创造了共享的学习环境,还导致识别和使用高效的提示词。
该领域的研究通常涉及分析和比较不同的提示词,以评估它们在各种情况下的有效性。例如,一项重要的研究表明,以"深呼吸,逐步解决这个问题"开头可以显著提高解决问题的效率。这凸显了精心选择的语言对生成式 AI 系统性能的影响。
掌握提示词的最有效用法,尤其是在 AI 技术快速发展的背景下,是一个持续的挑战。您应该认识到提示词工程的重要性,并考虑利用来自社区和研究的见解来改进提示词创建策略。
4.1 创建有效的提示词
在开发提示词时,整合几个关键组成部分以确保清晰和有效性非常重要:
-
指令: 向 AI 提供清晰直接的指令,类似于您与人交流的方式。这种清晰度对于帮助 AI "理解"期望至关重要。
-
外部上下文: 在必要时包含相关的背景信息或对 AI 响应的具体指导。这种"外部上下文"为提示词设定了框架,并帮助 AI 掌握整体情况。
-
用户输入: 这是直接的部分——用户构成提示词核心的直接请求或问题。
-
输出指示器: 这方面可能比较棘手。它涉及指定 AI 响应所需的格式,例如 JSON。但是,请注意,AI 可能并不总是严格遵守这种格式。例如,它可能在实际的 JSON 数据之前加上"这是您的 JSON"这样的短语,或者有时生成不准确的类 JSON 结构。
在制作提示词时,向 AI 提供预期问答格式的示例非常有益。这种做法有助于 AI "理解"您查询的结构和意图,从而产生更精确和相关的响应。虽然本文档没有深入探讨这些技术,但它们为在 AI 提示词工程中进一步探索提供了一个起点。
以下是供进一步研究的资源列表。
4.1.1 简单技巧
-
文本摘要: 将大量文本缩减为简洁的摘要,捕捉关键点和主要思想,同时省略不太重要的细节。
-
问答: 专注于根据用户提出的问题从提供的文本中得出具体答案。它是关于 pinpointing 和提取相关信息以响应查询。
-
文本分类: 根据文本内容将其系统地分类到预定义的类别或组中,分析文本并将其分配到最合适的类别。
-
对话: 创建交互式对话,使 AI 能够与用户进行来回交流,模拟自然的对话流程。
-
代码生成: 根据特定的用户需求或描述生成功能代码片段,将自然语言指令转换为可执行代码。
4.1.2 高级技巧
-
零样本、少样本学习: 使模型能够在几乎没有特定问题类型的先验示例的情况下做出准确的预测或响应,利用学习的泛化来理解和执行新任务。
-
思维链: 链接多个 AI 响应以创建连贯且具有上下文感知的对话。它帮助 AI 保持讨论的主线,确保相关性和连续性。
-
ReAct(推理+行动): 在这种方法中,AI 首先分析(推理)输入,然后确定最合适的行动方案或响应。它结合了理解与决策。
4.1.3 Microsoft Guidance
- 一个用于提示词创建和优化的框架: Microsoft 提供了一种结构化的方法来开发和优化提示词。该框架指导用户创建有效的提示词,从 AI 模型中引出所需的响应,从而优化交互的清晰度和效率。
五、令牌
令牌在 AI 模型处理文本的方式中至关重要,它充当一座桥梁,将单词(正如我们所理解的)转换为 AI 模型可以处理的格式。这种转换分两个阶段发生:单词在输入时被转换为令牌,然后这些令牌在输出时被转换回单词。
令牌化,即将文本分解为令牌的过程,是 AI 模型理解和处理语言的基础。AI 模型使用这种令牌化格式来理解和响应提示词。
为了更好地理解令牌,可以将它们视为单词的一部分。通常,一个令牌大约代表一个单词的四分之三。例如,莎士比亚全集大约有 90 万单词,将转换为大约 120 万个令牌。
可以尝试使用 OpenAI Tokenizer UI 来查看单词如何转换为令牌。
除了在 AI 处理中的技术作用外,令牌还具有实际意义,特别是在计费和模型能力方面:
-
计费: AI 模型服务通常基于令牌使用量计费。输入(提示词)和输出(响应)都计入总令牌数,这使得较短的提示词更具成本效益。
-
模型限制: 不同的 AI 模型具有不同的令牌限制,定义了它们的"上下文窗口"——它们一次可以处理的最大信息量。例如,GPT-3 的限制是 4K 令牌,而像 Claude 2 和 Meta Llama 2 等其他模型的限制是 100K 令牌,一些研究模型可以处理高达 100 万令牌。
-
上下文窗口: 模型的令牌限制决定了其上下文窗口。超过此限制的输入不会被模型处理。发送仅包含最小有效信息集进行处理至关重要。例如,当询问"哈姆雷特"时,无需包含莎士比亚所有其他作品的令牌。
-
响应元数据: 来自 AI 模型的响应的元数据包括使用的令牌数量,这是管理使用情况和成本的重要信息。
更多推荐


所有评论(0)