提示

提示是引导 AI 模型生成特定输出的输入。这些提示的设计和措辞会极大地影响模型的响应。

在 Spring AI 中与 AI 模型交互的最低级别,处理 Spring AI 中的提示有点类似于管理 Spring MVC 中的“视图”。这涉及创建包含动态内容占位符的扩展文本。然后根据用户请求或应用程序中的其他代码替换这些占位符。另一个类比是包含某些表达式占位符的 SQL 语句。

随着 Spring AI 的发展,它将引入更高层次的抽象来与 AI 模型进行交互。本节中描述的基础类可以比作 JDBC 在其作用和功能方面的类比。例如,ChatModel 类类似于 JDK 中的核心 JDBC 库。在此基础上,Spring AI 可以提供类似于 JdbcTemplate、Spring Data Repositories 的辅助类,并最终提供更高级的结构,如考虑与模型过去交互的 ChatEngines 和 Agents。

提示的结构在 AI 领域随着时间的推移而不断发展。最初,提示只是简单的字符串。随着时间的推移,它们发展到包括特定输入的占位符,例如“USER:”,AI 模型会识别这些占位符。OpenAI 通过将多个消息字符串分类为不同的角色来引入更多结构到提示中,然后再由 AI 模型处理这些提示。

API 概述

提示

通常使用 `ChatModel` 的 `call` 方法,该方法接收一个 `Prompt` 实例并返回一个 `ChatResponse`。

`Prompt` 类充当一个容器,用于组织一系列 `Message` 对象,每个对象构成整体提示的一个片段。每个 `Message` 在提示中都扮演着独特的角色,其内容和意图各不相同。这些角色可以包含各种元素,从用户查询到 AI 生成的响应或相关背景信息。这种安排使与 AI 模型进行复杂而详细的交互成为可能,因为提示是由多个消息构建的,每个消息都被分配了一个特定角色来在对话中发挥作用。

以下是 `Prompt` 类的简化版本,为了简洁起见,省略了构造函数和实用程序方法

public class Prompt {

    private final List<Message> messages;

    // constructors and utility methods omitted
}

消息

`Message` 接口封装了一个文本消息,一个作为 `Map` 的属性集合,一个称为 `MessageType` 的分类,以及一个用于多模态模型的媒体对象列表。接口定义如下

public interface Message extends Node<String> {

	String getContent();

	List<Media> getMedia();

	MessageType getMessageType();

}

而 `Node` 接口是

public interface Node<T> {

    T getContent();

    Map<String, Object> getMetadata();
}

`Message` 接口的各种实现对应于 AI 模型可以处理的不同类别消息。一些模型,例如来自 OpenAI 的模型,根据对话角色区分消息类别。这些角色实际上由 `MessageType` 映射,如下所述。

角色

AI 中提示的演变已经从基本的、简单的文本过渡到更组织化和更复杂的格式,具有特定的角色和结构。

最初,提示只是简单的字符串——只是一些文本行。随着时间的推移,这演变成在这些字符串中包含特定的占位符,例如“USER:”,AI 模型可以识别并相应地做出反应。这是朝着更结构化的提示迈出的一步。

然后 OpenAI 引入了一种更有条理的方法。在他们的模型中,提示不仅仅是单个字符串,而是一系列消息。每个消息虽然仍然以文本形式存在,但都被分配了一个特定的角色。这些角色对消息进行分类,为 AI 模型阐明每个提示片段的上下文和目的。这种结构化方法增强了与 AI 交互的细微差别和有效性,因为提示的每个部分都在交互中扮演着独特且明确的角色。

主要角色是

  • 系统角色:指导 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);
	}

}

提示模板

Spring AI 中提示模板的关键组件是 PromptTemplate 类。此类使用由 Terence Parr 开发的 OSS StringTemplate 引擎来构建和管理提示。PromptTemplate 类旨在简化结构化提示的创建,这些提示随后被发送到 AI 模型进行处理。

public class PromptTemplate implements PromptTemplateActions, PromptTemplateMessageActions {

    // Other methods to be discussed later
}

此类实现的接口支持提示创建的不同方面。

PromptTemplateStringActions 专注于创建和渲染提示字符串,代表最基本的提示生成形式。

PromptTemplateMessageActions 专为通过生成和操作消息对象来创建提示而量身定制。

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(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>,其中每个映射条目是提示模板中的占位符及其关联的动态值。

示例用法

下面展示了从 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 角色研讨会 的示例。

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,并传入占位符值,从而构建 Prompt 实例。然后将带有角色 user 的消息与带有角色 system 的消息组合起来形成提示。最后将提示传递给 ChatModel 以获取生成式响应。

使用资源代替原始字符串

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);

提示工程

在生成式 AI 中,创建提示是开发人员的一项重要任务。这些提示的质量和结构会极大地影响 AI 输出的有效性。在设计周到的提示上投入时间和精力可以极大地提高 AI 的结果。

在 AI 社区中,分享和讨论提示是一种常见的做法。这种协作方法不仅创造了一个共享的学习环境,而且还导致了识别和使用高效提示。

该领域的研究所涉及分析和比较不同的提示,以评估它们在各种情况下的有效性。例如,一项重要的研究表明,以“深呼吸,一步一步解决这个问题”开头提示可以显著提高解决问题的效率。这突出了精心选择的语言对生成式 AI 系统性能的影响。

随着 AI 技术的快速发展,掌握提示的最有效使用方法是一个持续的挑战。您应该认识到提示工程的重要性,并考虑使用来自社区和研究的见解来改进提示创建策略。

创建有效的提示

在开发提示时,重要的是要整合几个关键组件以确保清晰度和有效性

  • 说明:向 AI 提供清晰直接的说明,就像您与人交流一样。这种清晰度对于帮助 AI “理解”预期内容至关重要。

  • 外部上下文:在必要时,包括相关的背景信息或 AI 响应的具体指导。这种“外部上下文”为提示提供框架,并帮助 AI 理解整体场景。

  • 用户输入: 这是最直接的部分 - 用户的直接请求或问题,构成了提示的核心。

  • 输出指示: 这方面可能很棘手。它涉及指定 AI 响应的期望格式,例如 JSON。但是,请注意,AI 可能并不总是严格遵守这种格式。例如,它可能会在实际 JSON 数据之前加上“这是你的 JSON”之类的短语,或者有时生成类似 JSON 的结构,但并不准确。

在编写提示时,向 AI 提供预期问答格式的示例可能非常有益。这种做法有助于 AI “理解”查询的结构和意图,从而产生更准确、更相关的响应。虽然本文档没有深入探讨这些技术,但它们为进一步探索 AI 提示工程提供了一个起点。

以下是供进一步调查的资源列表。

简单技术

  • 文本摘要:
    将大量文本缩减为简洁的摘要,捕捉关键点和主要思想,同时省略不太重要的细节。

  • 问答:
    专注于根据用户提出的问题从提供的文本中得出特定答案。它旨在根据查询找出并提取相关信息。

  • 文本分类:
    系统地将文本分类到预定义的类别或组中,分析文本并根据其内容将其分配到最合适的类别。

  • 对话:
    创建交互式对话,AI 可以与用户进行来回交流,模拟自然的对话流程。

  • 代码生成:
    根据特定的用户需求或描述生成功能代码片段,将自然语言指令转换为可执行代码。

高级技术

  • 零样本学习少样本学习:
    使模型能够对特定问题类型的示例很少或没有的情况下做出准确的预测或响应,通过学习的泛化来理解和执行新任务。

  • 思维链:
    将多个 AI 响应链接起来,以创建连贯且具有上下文意识的对话。它有助于 AI 保持讨论的主题,确保相关性和连续性。

  • ReAct (推理 + 行动):
    在这个方法中,AI 首先分析(推理)输入,然后确定最合适的行动方案或响应。它将理解与决策结合在一起。

微软指南

  • 提示创建和优化框架:
    微软提供了一种结构化的方法来开发和改进提示。此框架指导用户创建有效的提示,以从 AI 模型中引出所需的响应,优化交互以实现清晰度和效率。

令牌

令牌在 AI 模型处理文本的方式中至关重要,充当桥梁,将单词(我们理解的)转换为 AI 模型可以处理的格式。此转换分两个阶段进行:单词在输入时转换为令牌,然后这些令牌在输出时转换回单词。

令牌化,将文本分解为令牌的过程,是 AI 模型理解和处理语言的基础。AI 模型使用这种令牌化格式来理解和响应提示。

为了更好地理解令牌,可以将它们视为单词的一部分。通常,一个令牌大约代表一个单词的四分之三。例如,莎士比亚的全部作品,总计约 900,000 个单词,将转换为约 120 万个令牌。

使用 OpenAI 令牌化 UI 进行实验,以了解单词如何转换为令牌。

令牌除了在 AI 处理中的技术作用外,还具有实际意义,尤其是在计费和模型功能方面。

  • 计费:AI 模型服务通常根据令牌使用情况进行计费。输入(提示)和输出(响应)都对总令牌数有贡献,因此更短的提示更具成本效益。

  • 模型限制:不同的 AI 模型具有不同的令牌限制,定义了它们的“上下文窗口”——它们一次可以处理的最大信息量。例如,GPT-3 的限制是 4K 个令牌,而其他模型(如 Claude 2 和 Meta Llama 2)的限制是 100K 个令牌,一些研究模型可以处理高达 100 万个令牌。

  • 上下文窗口:模型的令牌限制决定了它的上下文窗口。超过此限制的输入不会被模型处理。至关重要的是,只发送最少有效的信息集进行处理。例如,当询问“哈姆雷特”时,无需包含莎士比亚所有其他作品中的令牌。

  • 响应元数据:AI 模型响应的元数据包括使用的令牌数量,这是管理使用情况和成本的重要信息。