Spring AI Prompts
概述
提示(Prompts)是指导AI模型生成特定输出的输入。这些提示的设计和措辞显著影响模型的响应。
在Spring AI中与AI模型的最低级别的交互中,处理提示类似于在Spring MVC中管理"View"。这涉及创建带有动态内容占位符的扩展文本。然后根据用户请求或应用程序中的其他代码替换这些占位符。另一个类比是包含某些表达式占位符的SQL语句。
随着Spring AI的发展,它将引入与AI模型交互的更高层次抽象。本节描述的基础类在角色和功能方面可以比作JDBC。例如,ChatModel类类似于JDK中的核心JDBC库。ChatClient类可以比作JdbcClient,构建在ChatModel之上,并通过Advisor提供更高级的构造来考虑与模型的过去交互,用额外的上下文文档增强提示,并引入代理行为。
提示的结构在AI领域随着时间不断演进。最初,提示是简单的字符串。随着时间的推移,它们发展到包含特定输入的占位符,如"USER:",AI模型可以识别。OpenAI通过在AI模型处理之前将多个消息字符串分类到不同的角色中,为提示引入了更多的结构。
API概述
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;
}
Message
Message接口封装了Prompt文本内容、元数据属性集合和称为MessageType的分类。
接口定义如下:
public interface Content {
String getContent();
Map<String, Object> getMetadata();
}
public interface Message extends Content {
MessageType getMessageType();
}
多模态消息类型还实现了提供Media内容对象列表的MediaContent接口。
public interface MediaContent extends Content {
Collection<Media> getMedia();
}
Message接口的各种实现对应于AI模型可以处理的不同消息类别。模型基于对话角色区分消息类别。
这些角色由MessageType有效映射,如下所述。
角色
每个消息都被分配特定的角色。这些角色对消息进行分类,阐明提示中每个部分的上下文和目的,为AI模型服务。这种结构化方法增强了与AI沟通的细微差别和有效性,因为提示的每个部分在交互中都扮演着独特和定义的角色。
主要角色是:
-
系统角色(System Role): 指导AI的行为和响应风格,为AI解释和回复输入设置参数或规则。这类似于在开始对话之前向AI提供指令。
-
用户角色(User Role): 代表用户的输入——他们的问题、命令或对AI的陈述。这个角色是基础,因为它构成了AI响应的基础。
-
助手角色(Assistant Role): AI对用户输入的响应。不仅仅是答案或反应,对于维持对话的流程至关重要。通过跟踪AI的先前响应(其"助手角色"消息),系统确保连贯和上下文相关的交互。助手消息可能还包含函数工具调用请求信息。这就像AI中的一个特殊功能,在需要时用于执行特定功能,如计算、获取数据或其他不仅仅是交谈的任务。
-
工具/函数角色(Tool/Function Role): 工具/函数角色专注于响应工具调用助手消息返回附加信息。
角色在Spring AI中表示为枚举,如下所示:
public enum MessageType {
USER("user"),
ASSISTANT("assistant"),
SYSTEM("system"),
TOOL("tool");
...
}
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>,其中映射键是提示模板中的占位符名称,值是要插入的动态内容。
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>,其中每个条目代表消息模板中的占位符及其对应的动态值。
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>,其中每个映射条目是提示模板中的占位符及其关联的动态值,以及聊天请求的特定选项。
示例用法
以下是一个取自PromptTemplates AI研讨会的简单示例。
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研讨会的另一个示例。
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创建带有系统角色的消息,传入占位符值。然后将带有user角色的消息与带有system角色的消息组合形成提示。然后将提示传递给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中,创建提示是开发者的关键任务。这些提示的质量和结构显著影响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提示工程提供了起点。
以下是进一步调查的资源列表。
简单技术
-
文本摘要: 将大量文本减少为简洁摘要,抓住关键点和主要思想,同时省略不太关键的细节。
-
问答: 专注于从提供的文本中得出特定答案,基于用户提出的问题。它涉及精确定位和提取相关信息以响应查询。
-
文本分类: 系统地将文本分类到预定义类别或组中,分析文本并根据其内容将其分配给最合适的类别。
-
对话: 创建交互式对话,AI可以与用户进行来回通信,模拟自然的对话流程。
-
代码生成: 基于特定用户需求或描述生成功能性代码片段,将自然语言指令转换为可执行代码。
高级技术
-
零样本(Zero-shot)、少样本学习(Few-shot Learning), [www.promptingguide.ai/techniques/…): 使模型能够以最少或没有特定问题类型的先前示例进行准确预测或响应,使用学习的泛化来理解和处理新任务。
-
思维链(Chain-of-Thought): 将多个AI响应链接起来,创建连贯且上下文感知的对话。它帮助AI保持讨论的线索,确保相关性和连续性。
-
ReAct(推理+行动): 在这种方法中,AI首先分析(推理)输入,然后确定最适当的行动方案或响应。它将理解与决策制定结合起来。
Microsoft指导
- 提示创建和优化框架: Microsoft提供了一种结构化的方法来开发和优化提示。该框架指导用户创建有效的提示,从AI模型引发所需的响应,优化交互的清晰度和效率。
Tokens
Token是AI模型处理文本的基础,充当将单词(如我们理解的)转换为AI模型可以处理的格式的桥梁。这种转换发生在两个阶段:单词在输入时转换为token,然后这些token在输出时转换回单词。
分词(tokenization),即将文本分解为token的过程,是AI模型理解和处理语言的基础。AI模型使用这种分词格式来理解和响应提示。
为了更好地理解token,可以将它们视为单词的部分。通常,一个token代表大约四分之三个单词。例如,莎士比亚的完整作品总共约900,000个单词,将转换为约120万个token。
尝试使用OpenAI Tokenizer UI来查看单词如何转换为token。
Token除了在AI处理中的技术作用外,还具有实际意义,特别是在计费和模型能力方面:
-
计费: AI模型服务通常基于token使用量计费。输入(提示)和输出(响应)都有助于总token计数,使较短的提示更具成本效益。
-
模型限制: 不同的AI模型有不同的token限制,定义它们的"上下文窗口"——它们一次可以处理的最大信息量。例如,GPT-3的限制是4K token,而像Claude 2和Meta Llama 2等其他模型的限制是100K token,一些研究模型可以处理多达100万个token。
-
上下文窗口: 模型的token限制决定了其上下文窗口。超过此限制的输入不会被模型处理。只发送最少的处理有效信息集至关重要。例如,在询问关于"哈姆雷特"时,无需包含莎士比亚所有其他作品的token。
-
响应元数据: AI模型响应的元数据包括使用的token数量,这是管理使用和成本的重要信息。