提示
提示是指导AI模型生成特定输出的输入。这些提示的设计和措辞显著影响模型的响应。
在Spring AI中与AI模型的交互的最低级别中,处理提示在Spring AI中的方式与在Spring MVC中管理“视图”有些相似。这涉及创建大量带有动态内容占位符的文本。然后根据用户请求或应用程序中的其他代码替换这些占位符。另一个类比是包含某些表达式占位符的SQL语句。
ChatClient类类似于JDK中的核心JDBC库。在此基础上,Spring AI可以提供类似于
JdbcTemplate
、Spring数据仓库等助手类,最终,提供更高级的构造,比如考虑与模型过去交互的ChatEngines和Agents。
随着时间的推移,在AI领域内的提示结构也在不断发展。最初,提示是简单的字符串。随着时间的推移,它们增加了针对特定输入的占位符,比如“USER:”,这是AI模型能够识别的。OpenAI甚至通过将多个消息字符串分类为不同角色,然后在被AI模型处理之前进一步结构化了提示。
API 概述
提示
通常使用ChatClient
的generate
方法,该方法接受一个Prompt
实例并返回一个ChatResponse
。
Prompt
类作为一个组织良好的消息系列的容器,每个消息都形成整个提示的一个段落,是很常见的。每条消息在提示中扮演着独特的角色,其内容和意图各不相同。这些角色可以涵盖各种元素,从用户的查询到AI生成的响应或相关的背景信息。这种排列使得与AI模型进行复杂而详细的交互成为可能,因为提示由多条消息构成,每条消息都被分配了在对话中扮演的特定角色。
以下是Prompt
类的一个简化版本,为了简洁起见省略了构造函数和实用方法:
public class Prompt {
private final List<Message> messages;
// 省略构造函数和实用方法
}
消息
Message
接口封装了一个文本消息,一个作为Map
的属性集合,一个称为MessageType
的分类,以及一个多媒体对象列表,用于那些是多模型的模型。该接口定义如下:
public interface Message {
String getContent();
List<Media> getMedia();
Map<String, Object> getProperties();
MessageType getMessageType();
}
Message
接口的各种实现对应于AI模型可以处理的不同类型的消息。一些模型,如来自OpenAI的模型,根据对话角色区分消息类别。这些角色通过MessageType
进行了有效映射,如下所讨论。
角色
AI提示的演变从基本、直接的文本过渡到了更有组织、更复杂的格式,具有特定的角色和结构。
最初,提示只是简单的字符串——一行文本。随着时间的推移,这种情况发展到了这些字符串中包含特定的占位符,比如“用户:”,AI模型可以据此识别并相应地回答。这是朝着更有结构的提示迈出的一步。
接着,OpenAI引入了更加有组织的方法。在他们的模型中,提示不仅仅是单个字符串,而是一系列消息。每条消息虽然仍然以文本形式存在,但被分配了特定的角色。这些角色对消息进行了分类,澄清了每个提示片段的上下文和目的,使得与AI的交流更加细致和有效,因为提示的每个部分在交互中都扮演着独特而明确定义的角色。
主要角色包括:
-
系统角色:引导AI的行为和响应风格,设定AI解释和回复输入的参数或规则。这相当于在开始对话之前向AI提供指令。
-
用户角色:代表用户的输入——他们的问题、命令或对AI的陈述。这个角色是基本的,因为它构成了AI回应的基础。
-
助手角色:AI对用户输入的响应。它不仅仅是一个答案或反应,对于维持对话的流畅性至关重要。通过跟踪AI先前的响应(它的“助手角色”消息),系统确保了连贯和与上下文相关的交互。
-
功能角色:该角色处理对话中的特定任务或操作。虽然系统角色设定了AI的整体行为,但功能角色侧重于执行用户要求的某些操作或命令。就像是AI中的特殊功能,当需要执行特定功能(如计算、获取数据或其他不仅限于对话的任务)时使用。这个角色使得AI除了对话回应之外还能提供实际帮助。
在Spring AI中,角色被表示为以下枚举
public enum MessageType {
USER("用户"),
ASSISTANT("助手"),
SYSTEM("系统"),
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);
}
}
提示模板
Spring AI 中提示模板的关键组件是 PromptTemplate
类。该类使用由 Terence Parr 开发的 StringTemplate 引擎来构建和管理提示。 PromptTemplate
类旨在促进结构化提示的创建,然后将其发送到 AI 模型进行处理。
public class PromptTemplate implements PromptTemplateActions, PromptTemplateMessageActions {
// 其他待讨论的方法
}
该类实现的接口支持提示创建的不同方面:
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 对象,用于静态或预定义消息内容。
方法 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>,其中每个映射条目都是提示模板中的占位符及其关联的动态值。
示例用法
下面是从PromptTemplates的AI研讨会中获取的一个简单示例。
PromptTemplate promptTemplate = new PromptTemplate("告诉我一个关于{形容词} {主题} 的笑话");
Prompt prompt = promptTemplate.create(Map.of("形容词", adjective, "主题", topic));
return chatClient.call(prompt).getResult();
下面是从角色的AI研讨会中获取的另一个示例。
String userText = """
告诉我有关盗版黄金时代的三名著名海盗以及他们的原因。
为每个海盗至少写一句话。
""";
Message userMessage = new UserMessage(userText);
String systemText = """
你是一个乐于助人的AI助手,帮助人们找到信息。
你的名字是{name}
你应该用你的名字以及{语音}的风格回复用户的请求。
""";
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
,然后将具有角色user
的消息与具有角色system
的消息结合起来形成提示。 然后将提示传递给ChatClient以获得生成响应。
使用资源而不是原始字符串
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);
提示工程
在生成式人工智能中,创建提示是开发者的一项关键任务。这些提示的质量和结构显著影响人工智能输出的效果。投入时间和精力设计周到的提示可以极大地提升人工智能的结果。
在人工智能社区中,分享和讨论提示是一种常见的做法。这种协作方式不仅创造了一个共享学习环境,还能发现并使用高效的提示。
该领域的研究常常涉及分析和比较不同的提示,以评估它们在各种情况下的有效性。例如,一项重要研究表明,以“深呼吸,逐步解决这个问题”开头的提示显著提高了问题解决的效率。这突显了语言选择对生成式人工智能系统性能的影响。
在人工智能技术迅速发展的背景下,把握提示的最有效用法是一个持续的挑战。你应该认识到提示工程的重要性,并考虑利用社区和研究的见解来改进提示创建策略。
创建有效提示
在开发提示时,集成几个关键组成部分以确保清晰度和有效性至关重要:
-
指令:向人工智能提供清晰直接的指令,就像你与一个人交流一样。这种清晰度对帮助人工智能“理解”预期结果至关重要。
-
外部背景:在必要时包含相关的背景信息或对人工智能响应的具体指导。这种“外部背景”框定了提示,并帮助人工智能理解整个情景。
-
用户输入:这是简单明了的部分 - 用户直接的请求或问题构成了提示的核心。
-
输出指示:这一方面可能有些棘手。它涉及指定人工智能响应的期望格式,例如 JSON。然而,请注意,人工智能可能并不总是严格遵循这种格式。例如,它可能在实际的 JSON 数据之前添加一个短语,如“这是你的 JSON”,或者有时生成一个不准确的类似 JSON 结构。
当制作提示时,向人工智能提供预期问题和答案格式的示例可能会极大地有益。这种做法有助于人工智能“理解”你的查询的结构和意图,从而产生更精确和相关的响应。虽然本文档未深入探讨这些技术,但它们为进一步探索人工智能提示工程提供了一个起点。
以下是进一步调查的资源列表。
高级技术
-
零样本, 少样本学习:
使模型能够在几乎没有先前的特定问题类型示例的情况下做出准确的预测或响应,利用学到的概括对新任务进行理解和行动。 -
思维链:
将多个AI响应链接起来,创建连贯且具有上下文意识的对话。它帮助AI保持对话的主线,确保相关性和连续性。 -
反应(Reason + Act):
在这种方法中,AI首先分析(推理)输入,然后确定最合适的行动或响应。它结合了理解和决策。
微软指导
-
提示创建和优化框架:
微软提供了一个结构化的方法来开发和优化提示。这个框架指导用户创建有效的提示,从而引发AI模型所需的响应,并优化互动以提高清晰度和效率。
标记
标记在AI模型处理文本中至关重要,它们充当了将单词(我们理解的方式)转换为AI模型可以处理的格式的桥梁。此转换分为两个阶段:单词在输入时被转换为标记,然后这些标记在输出时被再次转换回单词。
标记化,即将文本分解为标记的过程,对于AI模型理解和处理语言至关重要。AI模型使用这种标记化格式来理解并响应提示。
为了更好地理解标记,可以将它们视为单词的部分。通常,一个标记代表大约四分之三的单词。例如,莎士比亚的完整作品,总共约90万字,将会转换为约120万个标记。
通过OpenAI标记化器UI进行实验,看看单词是如何转换为标记的。
标记除了在AI处理中扮演的技术角色外,还具有实际的影响,尤其是在计费和模型能力方面:
-
计费:AI模型服务通常根据标记使用情况计费。输入(提示)和输出(响应)都会影响总标记数,因此较短的提示更具成本效益。
-
模型限制:不同的AI模型具有不同的标记限制,定义了它们的“上下文窗口” - 它们可以一次处理的最大信息量。例如,GPT-3的限制是4K标记,而其他模型如Claude 2和Meta Llama 2的限制为100K标记,一些研究模型可以处理高达100万标记。
-
上下文窗口:模型的标记限制决定了它的上下文窗口。超出此限制的输入不会被模型处理。只发送最小有效信息集进行处理至关重要。例如,询问“哈姆雷特”时,无需包含莎士比亚其他作品的标记。
-
响应元数据:来自AI模型的响应的元数据包括使用的标记数,这是管理使用和成本的关键信息。