提示 API

20 阅读7分钟

概念: 提示词是引导AI模型生成特定输出的输入

比如,把AI模型想象成一个厨师—— 这个厨师学过做各种菜,你给它食材和要求,它就能做出对应的菜品。AI 模型也是如此,它提前 “学” 了海量数据,你给它指令,它就能输出结果(比如写文案、算题、翻译)。提示:就是你给这个智能工具的具体指令。还是用厨师举例,你说 “我要一份番茄炒蛋,少盐”,这句话就是提示。

在 Spring AI 与 AI 模型的最低级交互时,处理提示在某种程度上类似于 Spring MVC 中管理“视图”。 这包括创建带有动态内容占位符的大量文本。 这些占位符随后根据用户请求或应用程序中的其他代码被替换。最初,提示词是简单的字符串。 随着时间推移,它们逐渐加入了特定输入的占位符,比如“USER:”,AI模型能识别的角色。 OpenAI 通过将多个消息字符串分类为不同角色,为提示引入了更多结构,然后再由 AI 模型处理。

提示

通常会使用call()方法聊天模型这需要一个提示实例并返回聊天回应.

提示类作为有组织序列的容器消息对象和请求聊天选项. 每消息在提示中具有独特的角色,内容和意图各异。 这些角色涵盖多种环节,从用户提问到AI生成的相关背景信息回复。 这种安排使得与AI模型的复杂且细致的互动成为可能,提示由多条消息构成,每条信息在对话中被赋予特定角色。

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

public class Prompt implements ModelRequest<List<Message>> {

    private final List<Message> messages;//多条不同角色的消息

    private ChatOptions chatOptions;//里面可以设置模型,温度值,最大tokens数等等
}

消息

消息接口封装了提示文本内容、元数据属性集合,以及一种称为消息类型.

接口定义如下:

public interface Content {

	/**
	 * Get the content of the message.
	 * @return the content of the message
	 */
	String getText();

	/**
	 * Get the metadata associated with the content.
	 * @return the metadata associated with the content
	 */
	Map<String, Object> getMetadata();

}

多模态消息类型还实现了<font style="color:rgb(25, 30, 30);">媒体内容</font>接口提供列表<font style="color:rgb(25, 30, 30);">媒体</font>内容对象。

public interface MediaContent extends Content {

	/**
	 * Get the media associated with the content.
	 */
	List<Media> getMedia();

}

各种实现<font style="color:rgb(25, 30, 30);">消息</font>界面对应于AI模型能够处理的不同类别消息。 模型根据对话角色区分消息类别。

此图还是旧,个别方法的有变动,依赖关系没有变,变动方法参考文档代码块

角色

每个消息都被分配了特定的角色。 这些角色负责对信息进行分类,明确提示中每个部分的上下文和目的,供AI模型使用。 这种结构化的方法增强了与AI沟通的细腻度和有效性,因为提示的每个部分在互动中都扮演着独特且明确的角色。

主要职责包括:

  • 系统角色:指导AI的行为和响应风格,设定AI如何解释和响应输入的参数或规则。这就像在发起对话前先给AI提供指令。
  • 用户角色:代表用户的输入——他们对AI的问题、命令或陈述。这一角色至关重要,因为它构成了人工智能应对的基础。
  • 助理角色:AI对用户输入的回应。 这不仅仅是一个回答或反应,更对于保持对话的流畅性至关重要。 通过追踪AI之前的回复(其“助理角色”消息),系统确保互动连贯且符合上下文。 助手消息也可能包含功能工具调用请求信息。 它就像AI中的一个特殊功能,用于执行特定功能,比如计算、获取数据或其他不仅仅是说话的任务。
  • 工具/功能角色:工具/功能角色专注于回复工具呼叫助手消息时返回更多信息。

Spring AI 中角色以枚举形式表示,如下所示

public enum MessageType {

	/**
	 * A {@link Message} of type {@literal user}, having the user role and originating
	 * from an end-user or developer.
	 * @see UserMessage
	 */
	USER("user"),

	/**
	 * A {@link Message} of type {@literal assistant} passed in subsequent input
	 * {@link Message Messages} as the {@link Message} generated in response to the user.
	 * @see AssistantMessage
	 */
	ASSISTANT("assistant"),

	/**
	 * A {@link Message} of type {@literal system} passed as input {@link Message
	 * Messages} containing high-level instructions for the conversation, such as behave
	 * like a certain character or provide answers in a specific format.
	 * @see SystemMessage
	 */
	SYSTEM("system"),

	/**
	 * A {@link Message} of type {@literal function} passed as input {@link Message
	 * Messages} with function content in a chat application.
	 * @see ToolResponseMessage
	 */
	TOOL("tool");

提示模板

Spring AI 中提示模板化的一个关键组件是<font style="color:rgb(25, 30, 30);">提示模板</font>类,旨在促进结构化提示的创建,然后发送给 AI 模型进行处理

public class PromptTemplate implements PromptTemplateActions, PromptTemplateMessageActions {

	private static final Logger log = LoggerFactory.getLogger(PromptTemplate.class);

	private static final TemplateRenderer DEFAULT_TEMPLATE_RENDERER = StTemplateRenderer.builder().build();

	/**
	 * If you're subclassing this class, re-consider using the built-in implementation
	 * together with the new PromptTemplateRenderer interface, designed to give you more
	 * flexibility and control over the rendering process.
	 */
	private String template;

	private final Map<String, Object> variables = new HashMap<>();

	private final TemplateRenderer renderer;

	public PromptTemplate(Resource resource) {
		this(resource, new HashMap<>(), DEFAULT_TEMPLATE_RENDERER);
	}

	public PromptTemplate(String template) {
		this(template, new HashMap<>(), DEFAULT_TEMPLATE_RENDERER);
	}

该类使用<font style="color:rgb(25, 30, 30);">模板渲染器</font>API来渲染模板。默认情况下,Spring AI使用以下<font style="color:rgb(25, 30, 30);">StTemplateRenderer</font>该系统基于Terence Parr开发的开源StringTemplate引擎。模板变量通过语法标识,但你也可以配置分隔符使用其他语法。默认使用<font style="color:rgb(25, 30, 30);">{}</font>进行模版变量的替换

public class StTemplateRenderer implements TemplateRenderer {

	private static final Logger logger = LoggerFactory.getLogger(StTemplateRenderer.class);

	private static final String VALIDATION_MESSAGE = "Not all variables were replaced in the template. Missing variable names are: %s.";

	private static final char DEFAULT_START_DELIMITER_TOKEN = '{';

	private static final char DEFAULT_END_DELIMITER_TOKEN = '}';

	private static final ValidationMode DEFAULT_VALIDATION_MODE = ValidationMode.THROW;

	private static final boolean DEFAULT_VALIDATE_ST_FUNCTIONS = false;

	private final char startDelimiterToken;

	private final char endDelimiterToken;

	private final ValidationMode validationMode;

	private final boolean validateStFunctions;
}
public interface TemplateRenderer extends BiFunction<String, Map<String, Object>, String> {

	@Override
	String apply(String template, Map<String, Object> variables);

}

Spring AI使用了<font style="color:rgb(25, 30, 30);">模板渲染器</font>用于实际将变量替换到模板字符串中的接口。默认实现使用 [StringTemplate]。你可以提供你自己的实现<font style="color:rgb(25, 30, 30);">模板渲染器</font>如果你需要自定义逻辑。对于不需要模板渲染的场景(例如模板字符串已经完成),你可以使用提供的<font style="color:rgb(25, 30, 30);">NoOpTemplateRenderer</font>.

示例使用自定义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"));

示例用法

  1. 简单模版示例
package spring.ai;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/hello")
public class ChatController03 {

    private final ChatClient chatClient;

    public ChatController03(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    @GetMapping("/prompt")
    String generation() {
        PromptTemplate promptTemplate = new PromptTemplate("请告诉我一个 {adjective} 关于 {topic}");
        String messageContent = promptTemplate.create(java.util.Map.of("adjective", "电影", "topic", "张艺谋")).getContents();
        return this.chatClient.prompt()
                .user(messageContent)
                .call()
                .content();
    }

}

访问验证

2.基于角色消息示例

    @GetMapping("/rolePrompt")
    String rolePrompt() {
        String userText = """
            请告诉我三位黄金时代海盗的著名人物及其行为动机,为每位海盗至少写一句话。
        """;

        Message userMessage = new UserMessage(userText);

        String systemText = """
                你是乐于助人的AI助手,能帮人找信息。你的名字是{name},请用{voice}的风格回复我。
        """;

        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemText);
        Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", "小智", "voice","严肃" ));

        Prompt prompt = new Prompt(List.of(userMessage, systemMessage));

        List<Generation> response = this.chatClient.prompt(prompt).call().chatResponse().getResults();
        return response.get(0).getOutput().getText();
    }

这展示了如何逐步建立<font style="color:rgb(25, 30, 30);">提示</font>,实例通过使用<font style="color:rgb(25, 30, 30);">系统提示模板</font>以创建<font style="color:rgb(25, 30, 30);">消息</font>系统角色传递占位符值。 角色传递的信息<font style="color:rgb(25, 30, 30);">用户</font>然后与角色的信息结合<font style="color:rgb(25, 30, 30);">系统提示</font>形成完整提示。 完整提示随后传递给ChatModel,以获得生成性回。

验证:

3.使用自定义模版渲染器

可以通过实现模板渲染器接口并将其传递给提示模板构造 函数。你也可以继续使用默认StTemplateRenderer但配置是自定义的。

默认情况下,模板变量通过语法标识。如果你打算在提示词中包含JSON,可能需要使用不同的语法以避免与JSON语法冲突。例如,你可以使用和分隔符。{}改为<``>

   @GetMapping("/customPrompt")
    String customPrompt() {
        PromptTemplate promptTemplate = PromptTemplate.builder()
                .renderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
                .template("""
            告诉我五部电影名称由某位导演<composer>导演的.
            """)
                .build();

        String prompt = promptTemplate.render(Map.of("composer", "张艺谋"));
        return this.chatClient.prompt(prompt)
                .call()
                .content();
    }

验证: