提示词(Prompts)
提示词是引导AI模型生成特定输出的输入内容。这些提示词的设计和措辞会显著影响模型的响应。
在Spring AI中与AI模型进行最低层次的交互时,处理提示词的方式与Spring MVC中管理"视图(View)"有些类似。这涉及到创建包含动态内容占位符的大量文本。然后,这些占位符会根据用户请求或应用程序中的其他代码被替换。另一个类比是包含特定表达式占位符的SQL语句。
随着Spring AI的发展,它将引入更高级别的抽象来与AI模型交互。本节中描述的基础类,在角色和功能上可以类比为JDBC。例如,ChatModel类类似于JDK中的核心JDBC库。ChatClient类可以类比为JdbcClient,它构建在ChatModel之上,并通过Advisor(顾问)提供了更高级的构造,用以考虑与模型过往的交互、用额外的上下文文档扩充提示词,以及引入代理行为(agentic behavior)。
在AI领域,提示词的结构随着时间的推移而演变。最初,提示词是简单的字符串。随着时间的推移,它们发展到包含特定输入的占位符,例如AI模型能够识别的"USER:"。OpenAI通过在处理之前将多个消息字符串分类到不同的角色中,为提示词引入了更多的结构。
API概览
Prompt(提示词)
通常使用ChatModel的call()方法,该方法接收一个Prompt实例并返回一个ChatResponse。
Prompt类充当一个容器,用于存放一系列有组织的Message(消息)对象和一个ChatOptions(聊天选项)请求。每个Message在提示词中都体现了一个独特的角色,其内容和意图各不相同。这些角色可以包含各种元素,从用户查询到AI生成的响应,再到相关的背景信息。这种安排使得与AI模型进行复杂且详细的交互成为可能,因为提示词是由多条消息构建而成的,每条消息在对话中都扮演着特定的角色。
以下是Prompt类的精简版本,为简洁起见,省略了构造函数和工具方法:
java
public class Prompt implements ModelRequest<List<Message>> {
private final List<Message> messages;
private ChatOptions chatOptions;
}
便捷方法
Prompt类提供了几个便捷方法,用于按角色访问消息:
-
单条消息访问:
getUserMessage():返回提示词中的最后一条用户消息,如果不存在则返回空的UserMessage。getSystemMessage():返回提示词中的第一条系统消息,如果不存在则返回空的SystemMessage。getLastUserOrToolResponseMessage():返回最后一条用户或工具响应消息,对于对话的连续性很有用。
-
多条消息访问:
getUserMessages():返回提示词中所有用户消息的列表,保持其顺序。getSystemMessages():返回提示词中所有系统消息的列表,保持其顺序。
这些方法在处理多轮对话或需要按角色处理消息时特别有用。
Message(消息)
Message接口封装了提示词的文本内容、一组元数据属性以及一个称为MessageType(消息类型)的分类。
接口定义如下:
java
public interface Content {
String getContent();
Map<String, Object> getMetadata();
}
public interface Message extends Content {
MessageType getMessageType();
}
多模态消息类型还实现了MediaContent接口,该接口提供了Media(媒体)内容对象的列表。
java
public interface MediaContent extends Content {
Collection<Media> getMedia();
}
Message接口的各种实现对应于AI模型可以处理的不同类别的消息。模型根据对话角色来区分消息类别。
Spring AI Message API

这些角色通过MessageType(如下所述)进行了有效映射。
角色(Roles)
每条消息都被分配了一个特定的角色。这些角色对消息进行分类,为AI模型阐明提示词中每个部分的上下文和目的。这种结构化的方法增强了与AI沟通的细微差别和有效性,因为提示词的每个部分在交互中都扮演着独特且明确的角色。
主要角色包括:
-
系统(System)角色:指导AI的行为和响应风格,为AI如何解释和回复输入设定参数或规则。这类似于在开始对话之前向AI提供指示。
-
用户(User)角色:代表用户的输入------他们向AI提出的问题、命令或陈述。这个角色是基础性的,因为它构成了AI响应的基础。
-
助手(Assistant)角色:AI对用户输入的响应。它不仅仅是一个答案或反应,对于保持对话的流畅性也至关重要。通过跟踪AI之前的响应(其"助手角色"消息),系统确保了连贯且与上下文相关的交互。助手消息也可能包含功能工具调用(Function Tool Call)请求信息。这就像AI中的一个特殊功能,在需要执行特定功能(如计算、获取数据或其他超出单纯对话的任务)时使用。
-
工具/功能(Tool/Function)角色:工具/功能角色专注于响应工具调用助手消息而返回附加信息。
角色在Spring AI中表示为一个枚举,如下所示:
java
public enum MessageType {
USER("user"),
ASSISTANT("assistant"),
SYSTEM("system"),
TOOL("tool");
...
}
PromptTemplate(提示词模板)
Spring AI中提示词模板化的关键组件是PromptTemplate类,它旨在促进创建结构化的提示词,然后将其发送给AI模型进行处理。
java
public class PromptTemplate implements PromptTemplateActions, PromptTemplateMessageActions {
// 其他方法将在后面讨论
}
此类使用TemplateRenderer(模板渲染器)API来渲染模板。默认情况下,Spring AI使用StTemplateRenderer实现,该实现基于由Terence Parr开发的开源StringTemplate引擎。模板变量由{}语法标识,但您也可以配置分隔符以使用其他语法。
java
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渲染器的示例,分隔符为'<'和'>'
java
PromptTemplate promptTemplate = PromptTemplate.builder()
.renderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
.template("""
告诉我5部由<composer>创作配乐的电影名称。
""")
.build();
String prompt = promptTemplate.render(Map.of("composer", "John Williams"));
此类实现的接口支持提示词创建的不同方面:
-
PromptTemplateStringActions专注于创建和渲染提示词字符串,代表最基本形式的提示词生成。 -
PromptTemplateMessageActions专为通过生成和操作Message对象来创建提示词而定制。 -
PromptTemplateActions旨在返回Prompt对象,该对象可以传递给ChatModel以生成响应。
虽然这些接口在许多项目中可能不会被广泛使用,但它们展示了提示词创建的不同方法。
实现的接口如下:
java
public interface PromptTemplateStringActions {
String render();
String render(Map<String, Object> model);
}
-
方法
String render():将提示词模板渲染为最终字符串格式,无需外部输入,适用于没有占位符或动态内容的模板。 -
方法
String render(Map<String, Object> model):增强渲染功能以包含动态内容。它使用Map<String, Object>,其中映射键是提示词模板中的占位符名称,值是要插入的动态内容。
java
public interface PromptTemplateMessageActions {
Message createMessage();
Message createMessage(List<Media> mediaList);
Message createMessage(Map<String, Object> model);
}
-
方法
Message createMessage():创建没有额外数据的Message对象,用于静态或预定义的消息内容。 -
方法
Message createMessage(List<Media> mediaList):创建包含静态文本和媒体内容的Message对象。 -
方法
Message createMessage(Map<String, Object> model):扩展消息创建以整合动态内容,接受Map<String, Object>,其中每个条目代表消息模板中的占位符及其对应的动态值。
java
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>,其中每个映射条目是提示词模板中的占位符及其关联的动态值。 -
方法
Prompt create(Map<String, Object> model, ChatOptions modelOptions):扩展提示词创建能力以包含动态内容,接受Map<String, Object>(其中每个映射条目是提示词模板中的占位符及其关联的动态值)和特定的聊天请求选项。
使用示例
一个来自AI研讨会关于PromptTemplates的简单示例如下所示。
java
PromptTemplate promptTemplate = new PromptTemplate("给我讲一个关于{topic}的{adjective}笑话");
Prompt prompt = promptTemplate.create(Map.of("adjective", adjective, "topic", topic));
return chatModel.call(prompt).getResult();
另一个来自AI研讨会关于Roles的示例如下所示。
java
String userText = """
告诉我三位来自海盗黄金时代的著名海盗以及他们做了什么。
为每位海盗至少写一句话。
""";
Message userMessage = new UserMessage(userText);
String systemText = """
你是一个乐于助人的AI助手,帮助人们查找信息。
你的名字是{name}
你应该用你的名字并以{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并传入占位符值,来构建Prompt实例。然后,将用户角色的消息与系统角色的消息组合形成提示词。最后,将提示词传递给ChatModel以获取生成响应。
使用自定义模板渲染器
您可以通过实现TemplateRenderer接口并将其传递给PromptTemplate构造函数来使用自定义模板渲染器。您也可以继续使用默认的StTemplateRenderer,但使用自定义配置。
默认情况下,模板变量由{}语法标识。如果您计划在提示词中包含JSON,您可能希望使用不同的语法以避免与JSON语法冲突。例如,您可以使用<和>分隔符。
java
PromptTemplate promptTemplate = PromptTemplate.builder()
.renderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
.template("""
告诉我5部由<composer>创作配乐的电影名称。
""")
.build();
String prompt = promptTemplate.render(Map.of("composer", "John Williams"));
使用资源代替原始字符串
Spring AI支持org.springframework.core.io.Resource抽象,因此您可以将提示词数据放在一个文件中,并直接在PromptTemplate中使用。例如,您可以在Spring管理的组件中定义一个字段来获取资源。
java
@Value("classpath:/prompts/system-message.st")
private Resource systemResource;
然后直接将此资源传递给SystemPromptTemplate。
java
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource);
提示词工程(Prompt Engineering)
在生成式AI中,提示词的创建对开发者来说是一项至关重要的任务。这些提示词的质量和结构显著影响AI输出的有效性。投入时间和精力设计周到的提示词可以极大地改善AI的结果。
在AI社区中,分享和讨论提示词是一种常见的做法。这种协作方法不仅创造了一个共享的学习环境,而且还能识别和使用高效的提示词。
这一领域的研究通常涉及分析和比较不同的提示词,以评估它们在不同情况下的有效性。例如,一项重要研究表明,以"深呼吸,逐步解决这个问题"开头的提示词显著提高了问题解决的效率。这突显了精心选择的语言对生成式AI系统性能的影响。
掌握最有效地使用提示词的方法,尤其是在AI技术快速发展的背景下,是一个持续的挑战。您应该认识到提示词工程的重要性,并考虑利用社区和研究的见解来改进提示词创建策略。
创建有效的提示词
在开发提示词时,整合几个关键组件以确保清晰性和有效性非常重要:
-
指令(Instructions):向AI提供清晰直接的指令,类似于与人类沟通的方式。这种清晰性对于帮助AI"理解"期望是什么至关重要。
-
外部上下文(External Context):必要时,包含相关的背景信息或针对AI响应的具体指导。这种"外部上下文"构建了提示词的框架,并帮助AI把握整体场景。
-
用户输入(User Input):这是简单的部分------构成提示词核心的用户直接请求或问题。
-
输出指示(Output Indicator):这方面可能比较棘手。它涉及到指定AI响应的期望格式,例如JSON。但是,请注意AI可能并不总是严格遵守此格式。例如,它可能在实际JSON数据之前添加类似"这是您的JSON"的短语,或者有时生成不准确的类JSON结构。
在制作提示词时,向AI提供预期的问答格式示例可能非常有益。这种做法有助于AI"理解"您查询的结构和意图,从而产生更精确和相关的响应。虽然本文档未深入探讨这些技术,但它们为进一步探索AI提示词工程提供了一个起点。
以下是一些供进一步研究的资源列表。
简单技术
-
文本摘要(Text Summarization):将大量文本缩减为简洁的摘要,捕捉关键点和主要思想,同时省略不太重要的细节。
-
问答(Question Answering):侧重于根据提供的文本,基于用户提出的问题得出具体答案。这涉及根据查询精确定位和提取相关信息。
-
文本分类(Text Classification):系统地将文本归类到预定义的类别或组中,分析文本并根据其内容将其分配到最合适的类别。
-
对话(Conversation):创建交互式对话,AI可以与用户进行来回沟通,模拟自然的对话流程。
-
代码生成(Code Generation):根据特定的用户需求或描述生成功能性的代码片段,将自然语言指令转换为可执行代码。
高级技术
-
零样本(Zero-shot)、少样本(Few-shot)学习:使模型能够在没有或仅有极少特定问题类型先验示例的情况下,做出准确的预测或响应,利用学到的泛化能力来理解和处理新任务。
-
思维链(Chain-of-Thought):链接多个AI响应,以创建连贯且具有上下文感知的对话。它帮助AI保持讨论的脉络,确保相关性和连续性。
-
ReAct(推理+行动,Reason + Act):在这种方法中,AI首先分析(推理)输入,然后确定最合适的行动或响应方案。它结合了理解和决策。
微软指南(Microsoft Guidance)
- 提示词创建和优化框架:微软提供了一种结构化的方法来开发和优化提示词。该框架指导用户创建有效的提示词,以从AI模型中引出所需的响应,优化交互的清晰度和效率。
Tokens(令牌)
Tokens在AI模型处理文本的方式中至关重要,它充当着将单词(我们理解的形式)转换为AI模型可以处理的格式的桥梁。这种转换分两个阶段进行:输入时,单词被转换为tokens;输出时,这些tokens再被转换回单词。
分词(Tokenization),即把文本分解为tokens的过程,是AI模型理解和处理语言的基础。AI模型使用这种tokenized格式来理解和响应提示词。
为了更好地理解tokens,可以把它们想象成单词的一部分。通常,一个token代表大约一个单词的四分之三。例如,莎士比亚的全部作品,总计约90万字,将转换为大约120万个tokens。
可以尝试使用OpenAI Tokenizer UI来查看单词是如何转换为tokens的。
Tokens除了在AI处理中的技术角色外,还有实际影响,特别是在计费和模型能力方面:
-
计费(Billing):AI模型服务通常根据token使用量计费。输入(提示词)和输出(响应)都计入总token数,这使得更短的提示词更具成本效益。
-
模型限制(Model Limits):不同的AI模型有不同的token限制,这定义了它们的"上下文窗口"(context window)------即它们一次可以处理的最大信息量。例如,GPT-3的限制是4K tokens,而Claude 2和Meta Llama 2等其他模型的限制是100K tokens,一些研究模型甚至可以处理高达100万tokens。
-
上下文窗口(Context Window):模型的token限制决定了其上下文窗口。超过此限制的输入将不会被模型处理。只发送最少量有效的信息进行处理至关重要。例如,当询问关于"哈姆雷特"的问题时,没有必要包含莎士比亚所有其他作品的tokens。
-
响应元数据(Response Metadata):来自AI模型的响应的元数据包括使用的tokens数量,这是管理使用情况和成本的重要信息。