Prompts
提示是指导 AI 模型生成特定输出的输入。这些提示的设计和措辞显著地影响了模型的响应。
在 Spring AI 中与 AI 模型交互的最低级别上,在 Spring AI 中处理提示有点类似于在 Spring MVC 中管理“视图”。这涉及使用动态内容的占位符创建大量文本。这些占位符随后基于用户请求或应用程序中的其他代码进行替换。另一个类似之处是包含用于特定表达式的占位符的 SQL 语句。
随着 Spring AI 的发展,它将为与 AI 模型交互引入更高的抽象级别。本节中描述的基础类在它们的角色和功能方面可以比作 JDBC。例如,ChatClient
类类似于 JDK 中的核心 JDBC 库。在此基础上,Spring AI 可以提供类似于 JdbcTemplate
、Spring Data Repositories,最终更高级的构造(如考虑与模型的过往交互的 ChatEngines 和 Agents)的帮助器类。
提示的结构随着时间在 AI 领域内发生了演变。最初,提示是简单的字符串。随着时间的推移,它们逐渐包含用于特定输入的占位符,例如 AI 模型识别的“USER:”。OpenAI 通过在 AI 模型处理之前将多个消息字符串分类为不同的角色,为提示引入了更加结构化的内容。
API Overview
Prompt
通常使用 ChatClient
的 generate
方法,该方法接收 Prompt
实例并返回 ChatResponse
。
Prompt
类用作有组织的 Message 对象序列的容器,每个对象组成整体提示的一部分。每个 Message 都体现了提示中一个独特的角色,其内容和意图各不相同。这些角色可以包含各种元素,从用户询问到 AI 生成的响应或相关背景信息。这种安排使与 AI 模型进行复杂且详细的交互成为可能,因为提示是由多条消息构建的,每条消息都在对话中扮演特定的角色。
下面是 Prompt
类的截断版本,为简洁起见,省略了构造函数和实用方法:
public class Prompt {
private final List<Message> messages;
// constructors and utility methods omitted
}
Message
Message
接口封装一个文本消息、一个作为 Map
的属性集合、称为 MessageType
的分类以及一个用于多模态模型的媒体对象列表。该接口定义如下:
public interface Message {
String getContent();
List<Media> getMedia();
Map<String, Object> getProperties();
MessageType getMessageType();
}
Message
接口的各种实现对应于 AI 模型可以处理的不同类别的消息。某些模型(如来自 OpenAI 的模型)根据会话角色区分消息类别。这些角色实际上是由 MessageType
映射的,如下所述。
Roles
人工智能提示的演变从基本的、直接的文本发展到更具条理和复杂的格式,它具有特定的角色和结构。
最初,提示只是简单的字符串——也就是文本行。随着时间的推移,其发展到在这些字符串中包括特定的占位符,比如“USER:”,人工智能模型能够识别它并做出相应的回应。这是走向更结构化提示的一步。
随后,OpenAI引入了一种更有条理的方法。在他们的模型中,提示不仅仅是单个字符串,还包括一系列消息。每条消息虽然仍以文本形式出现,但被分配了一个特定的角色。这些角色对消息进行分类,阐明了人工智能模型提示每一部分的背景和目的。这种结构化的方法增强了与人工智能交流的细微差别和有效性,因为提示的每一部分在交互中都扮演着不同而明确的角色。
主要角色有:
-
系统角色:指导 AI 的行为和响应风格,设置 AI 解释和回复输入方式的参数或规则。这类似于在开启对话前向 AI 提供指令。
-
用户角色:代表用户输入——他们对 AI 的问题、命令或陈述。此角色非常重要,因为它是 AI 响应的基础。
-
助理角色:AI 对用户输入的响应。它不仅仅是一种答案或反应,对于维持对话的流程至关重要。通过跟踪 AI 先前的响应(其“助理角色”消息),系统可确保交互具有连贯性和与上下文相关。
-
功能角色:此角色负责对话期间的特定任务或操作。虽然系统角色设置了 AI 的整体行为,但功能角色专注于执行用户请求的某些操作或命令。这就像 AI 中的一项特殊功能,在需要时用于执行特定功能,如计算、获取数据或除单纯交谈之外的其他任务。此角色允许 AI 除了提供对话式响应外还提供实用帮助。
角色在Spring AI中表示为一个枚举,如下所示
public enum MessageType {
USER("user"),
ASSISTANT("assistant"),
SYSTEM("system"),
FUNCTION("function");
private final String value;
MessageType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static MessageType fromValue(String value) {
for (MessageType messageType : MessageType.values()) {
if (messageType.getValue().equals(value)) {
return messageType;
}
}
throw new IllegalArgumentException("Invalid MessageType value: " + value);
}
}
PromptTemplate
Spring AI中提示模板化的关键组件是`PromptTemplate`类。该类使用Terence Parr开发的StringTemplate引擎,用于构造和管理提示。`PromptTemplate`类旨在促进结构化提示的创建,然后将这些提示发送到人工智能模型进行处理
public class PromptTemplate implements PromptTemplateActions, PromptTemplateMessageActions {
// Other methods to be discussed later
}
该类实现的接口支持提示创建的各个方面:
`PromptTemplateStringActions`专注于创建和呈现提示字符串,代表最基本的提示生成形式。
`PromptTemplateMessageActions`专为通过生成和处理Message对象进行提示创建而设计。
`PromptTemplateActions`旨在返回Prompt对象,该对象可以传递给ChatClient以生成响应。
虽然在许多项目中可能不会广泛使用这些界面,但它们展示了提示创建的不同方法。
实现的接口
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(Map<String, Object> model);
}
方法`Message createMessage()`:创建一个不包含附加数据的消息对象,用于静态或预定义的消息内容。
方法`Message createMessage(Map<String, Object> model)`:扩展消息创建以集成动态内容,它接受一个Map<String, Object>,其中每个条目表示消息模板中的一个占位符及其对应的动态值。
public interface PromptTemplateActions extends PromptTemplateStringActions {
Prompt create();
Prompt create(Map<String, Object> model);
}
方法`Prompt create()`:生成一个没有外部数据输入的Prompt对象,非常适合静态或预定义的提示。
方法`Prompt create(Map<String, Object> model)`:扩展提示创建能力以包含动态内容,它接受一个Map<String, Object>,其中每个映射条目是提示模板中的占位符及其关联的动态值。
Example Usage
以下所示是取自 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 chatClient.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 = chatClient.call(prompt).getResults();
此示例展示了如何使用 SystemPromptTemplate
来创建包含传递占位符值的系统角色的 Message
,以此构建 Prompt
实例。然后将具有角色 user
的消息与角色为 system
的消息组合起来,以形成提示。随后将提示传递给 ChatClient 以获得生成式响应。
Using resources instead of raw Strings
Spring AI 支持 org.springframework.core.io.Resource
抽象,因此,你可以在文件中放置可以直接在 PromptTemplates 中使用的提示数据。例如,可以在 Spring 托管组件中定义一个字段来检索 Resource。
@Value("classpath:/prompts/system-message.st")
private Resource systemResource;
然后将该资源直接传递给 SystemPromptTemplate
。
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource);
Prompt Engineering
在生成式 AI 中,提示的创建是开发人员的一项关键任务。这些提示的质量和结构会极大地影响 AI 的输出效果。投入时间和精力来设计经过深思熟虑的提示可以极大地改善 AI 的结果。
在 AI 社区中,分享和讨论提示是一种常见做法。这种协作方式不仅创建一个共享的学习环境,而且还导致识别和使用高效的提示。
这个领域的研究通常涉及分析和比较不同的提示,以评估它们在不同情况下的有效性。例如,一项重大的研究表明,以“深呼吸,一步一步解决这个问题”开头可以显著提高解决问题的效率。这突出了精心选择用语对生成式 AI 系统性能的影响。
掌握最有效的使用提示的方法,尤其是在 AI 技术快速发展的情况下,是一个持续的挑战。你应该认识到提示工程的重要性,并考虑利用社区和研究的见解来改进提示创建策略。
Creating effective prompts
在开发提示时,集成几个关键组件以确保清晰性和有效性非常重要:
-
Instructions:向 AI 提供清晰直接的指令,类似于你与人交流的方式。这种清晰性对于帮助 AI“理解”期望至关重要。
-
External Context:必要时为 AI 的响应提供相关背景信息或具体指导。此“外部上下文”构建了提示并帮助 AI 掌握整体场景。
-
User Input:这是简单明了的部分——用户的直接请求或问题构成了提示的核心。
-
Output Indicator:这方面可能有些棘手。它涉及指定 AI 响应的期望格式,如 JSON。但请注意 AI 可能不会始终严格遵守此格式。例如,它可能在实际 JSON 数据前缀加一个诸如“这是你的 JSON”的短语,或者有时会生成不准确的类似 JSON 的结构。
在编写提示时,向 AI 提供预期问题和答案格式的示例可能非常有益。这个做法能够帮助 AI“理解”查询的结构和意图,从而产生更准确和相关性的响应。虽然此文档并未深入研究这些技术,但它们为进一步探索 AI 提示工程提供了一个起点。
以下列出了一些供进一步调查的资源。
Simple Techniques
-
Text Summarization:将大量的文本简化为简洁的摘要,捕捉重点和主要观点,同时忽略不太重要的细节。
-
Question Answering:侧重于从提供的文本中衍生出具体的答案,基于用户提出的问题。它关于根据查询明确指出和提取相关信息。
-
Text Classification:系统地将文本分类到预定义的类别或分组中,分析文本并根据其内容将其分配到最合适的类别。
-
Conversation:创建交互式对话,其中人工智能可以与用户进行来回交流,模拟自然的对话流。
-
Code Generation:根据特定的用户要求或描述生成功能性的代码片段,将自然语言指令翻译成可执行代码。
Advanced Techniques
-
Zero-shot, Few-shot Learning:使模型能够对最小到没有特定问题类型先例的情况做出准确的预测或响应,利用学习到的概括理解和执行新任务。
-
Chain-of-Thought:链接多个人工智能响应以创建连贯且具有上下文意识的对话。它帮助人工智能维持讨论的主题,确保相关性和连续性。
-
ReAct (Reason + Act):在这种方法中,人工智能首先分析(推理)输入,然后确定最适当的行为或响应。它将理解与决策相结合。
Microsoft Guidance
-
Framework for Prompt Creation and Optimization:Microsoft 提供了一种开发和完善提示的结构化方法。此框架指导用户创建有效的提示,以从 AI 模型中引出所需的响应,以清晰和高效优化互动。
Tokens
令牌在 AI 模型处理文本中至关重要,充当将单词(正如我们理解的那样)转换成 AI 模型可以处理的格式的桥梁。此转换分两个阶段进行:输入时将单词转换为令牌,然后在输出中将这些令牌转换回单词。
标记化(将文本分解为标记的过程)是 AI 模型理解和处理语言的基础。AI 模型使用这种标记化格式来理解和响应提示。
为了更好地理解令牌,可以将它们视为单词的一部分。通常,一个令牌代表大约四分之三个单词。例如,莎士比亚的全部作品大约有 900,000 个单词,转换为大约 120 万个令牌。
使用 OpenAI Tokenizer UI 来试验如何将单词转换为标记。
除了在 AI 处理中的技术作用外,令牌还具有实际意义,尤其是在计费和模型功能方面:
-
计费:人工智能模型服务通常根据令牌使用情况进行计费。输入(提示)和输出(响应)都会计入令牌总数,从而使较短的提示更具成本效益。
-
模型限制:不同的 AI 模型有不同的令牌限制,定义它们的“上下文窗口”——它们可以一次处理的最大信息量。例如,GPT-3 的限制是 4K 个令牌,而 Claude 2 和 Meta Llama 2 等其他模型的限制是 100K 个令牌,一些研究模型可以处理多达 100 万个令牌。
-
上下文窗口:模型的令牌限制决定了它的上下文窗口。超过此限制的输入不会由模型处理。只发送最小有效的信息集合进行处理至关重要。例如,当询问“哈姆雷特”时,无需包含莎士比亚所有其他作品中的令牌。
-
响应元数据:来自人工智能模型的响应的元数据包括所用令牌的数量,这是管理使用情况和成本的重要信息。