Spring AI-02:玩转Prompt

书接上回:https://blog.csdn.net/qq_59109986/article/details/138930979

目前已经用 Spring AI 快速搭建了一个基础的 Application。这次就快速了解一下 Spring AI 里的 Prompt,主要针对如何使用框架里的 Prompt 相关的 API 的使用。

官方文档:https://docs.spring.io/spring-ai/reference/api/prompt.html

简单了解 Prompt

Prompt 是引导 AI 模型生成特定输出的输入信息。这些提示的设计和措辞极大地影响着模型的响应。说得再简单点,任何我们发给 AI 模型的信息都可以是 Prompt,例如上回我们问 ChatGPT:“你会不会唱跳 Rap 打篮球?”。
在AI领域中,Prompt 的结构随着时间演进,现在已经发展出了一门专门的学科——提示词工程。

学习了解提示词工程可参考提示工程指南https://www.promptingguide.ai/zh

最初的 Prompt 只是简单的字符串。随着时间推移,它们发展到包含特定输入的占位符,如“USER:”,AI模型能够识别这一标识。
Spring AI 将来可能也会引入更高层次的抽象来与 AI 模型进行交互,目前提供的基础类Prompt是我们调用 AI 模型接口时封装消息的主要工具类,即我们调用模型的call方法或是stream方法传入的参数类型大多数时候都是 Prompt 对象。
例如 ChatClient接口的 call 方法:

@FunctionalInterface
public interface ChatClient extends ModelClient<Prompt, ChatResponse> {

	default String call(String message) {
		Prompt prompt = new Prompt(new UserMessage(message));
		Generation generation = call(prompt).getResult();
		return (generation != null) ? generation.getOutput().getContent() : "";
	}

	@Override
	ChatResponse call(Prompt prompt);

}

例如StreamingChatClient接口的stream方法:

@FunctionalInterface
public interface StreamingChatClient extends StreamingModelClient<Prompt, ChatResponse> {

	default Flux<String> stream(String message) {
		Prompt prompt = new Prompt(message);
		return stream(prompt).map(response -> (response.getResult() == null || response.getResult().getOutput() == null
				|| response.getResult().getOutput().getContent() == null) ? ""
						: response.getResult().getOutput().getContent());
	}

	@Override
	Flux<ChatResponse> stream(Prompt prompt);

}

而上回的OpenAiChatClient类实现了上面的两个接口,重写的call方法和stream方法传入的参数都是 Prompt 对象。

API 速览

Prompt

Prompt 类的定义比较简单,总共也就 90 多行,重点看一下它的构造方法:

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

	private final List<Message> messages;

	private ChatOptions modelOptions;

	public Prompt(String contents) {
		this(new UserMessage(contents));
	}

	public Prompt(Message message) {
		this(Collections.singletonList(message));
	}

	public Prompt(List<Message> messages) {
		this.messages = messages;
	}

	public Prompt(String contents, ChatOptions modelOptions) {
		this(new UserMessage(contents), modelOptions);
	}

	public Prompt(Message message, ChatOptions modelOptions) {
		this(Collections.singletonList(message), modelOptions);
	}

	public Prompt(List<Message> messages, ChatOptions modelOptions) {
		this.messages = messages;
		this.modelOptions = modelOptions;
	}
}

官方的解释说:Prompt 类充当一系列有序 Message 对象的容器,每个对象构成 Prompt 的整体部分。每个 Message 在提示中扮演一个独特角色,其内容和目的各不相同。这些角色可以包括多种元素,从用户查询到 AI 生成的响应或相关的背景信息。这种安排使得与 AI 模型的交互变得复杂而详尽,因为提示由多个消息构建而成,每个消息在对话中被分配了特定的角色。
从 Prompt 类的定义中可以比较清楚的看出如何构建一个 Prompt 对象(这好像一句废话),值得注意的是两点:

  1. 可以传入一个 Message 对象或 Message 列表构建 Prompt 实例对象;
  2. 可以传入一个 Message 对象或对象列表,加上 ChatOptions 对象来构建 Prompt 实例。

第一点关于 Message 接口的内容先按下不表。对于第二点,官方文档中给出过这样的示例代码:

var openAiApi = new OpenAiApi(System.getenv("OPENAI_API_KEY"));

var chatClient = new OpenAiChatClient(openAiApi)
    .withDefaultOptions(OpenAiChatOptions.builder()
            .withModel("gpt-35-turbo")
            .withTemperature(0.4)
            .withMaxTokens(200)
        .build());

ChatResponse response = chatClient.call(
    new Prompt("Generate the names of 5 famous pirates."));

// Or with streaming responses
Flux<ChatResponse> response = chatClient.stream(
    new Prompt("Generate the names of 5 famous pirates."));

上回提到的需求:

我们开发了一个 AI 应用提供给用户,需要用户配置自己的 API key、自己选择模型、参数等配置选项

似乎也就可以通过 Prompt 来一步到位了。下面用一个简单的示例来验证:

    /**
     * 构建 Prompt 实例时附带 ChatOption 的调用方法
     *
     * @param api 设置 OpenAI 的 API key 的对象
     * @param prompt 构建了附带 ChatOption 的 Prompt 对象
     */
    public Map<String, String> singleCompletionByPromptWithOption (OpenAiApi api, Prompt prompt) {

        OpenAiChatClient openAiChatClient = new OpenAiChatClient(api);

        String response = openAiChatClient.call(prompt).getResult().getOutput().getContent();

        return Map.of(
                "Response", response
        );
    }

然后修改一下 Controller 里的manualConfigDemo方法:

    @GetMapping("/gpt/manual")
    public Map<String, String> manualConfigDemo (@RequestParam String api,
                                                 @RequestParam(defaultValue = "gpt-4") String chatModel,
                                                 @RequestParam(defaultValue = "1.0") String temperature,
                                                 @RequestParam(defaultValue = "10") String maxTokens,
                                                 @RequestParam(defaultValue = "你会不会唱跳Rap打篮球?") String message) {
        OpenAiApi openAiApi = new OpenAiApi(api);
        Float temp = Float.parseFloat(temperature);
        Integer maxToken = Integer.parseInt(maxTokens);

        ChatOptionsBean optionsBean = new ChatOptionsBean(chatModel, temp, maxToken);

        OpenAiChatOptions chatOptions = completionService.createChatOptions(optionsBean);

        Prompt prompt = new Prompt(new UserMessage(message), chatOptions);

        return completionService.singleCompletionByPromptWithOption(openAiApi, prompt);

    }

看到响应:

{
  "Response": "作为一个人工智能虚"
}

发现手动配置的参数确实起作用了(最明显: maxToken=10),能够正常调用。

Message

前面提到,构建 Prompt 实例对象很多时候需要的还是 Message 接口。先简单看一下接口的定义:

public interface Message {

	String getContent();

	List<Media> getMedia();

	Map<String, Object> getProperties();

	MessageType getMessageType();

}

然后是 Message 接口的主要实现类,其实实现 Message 接口的类目前只有AbstractMessage抽象类,而其他的五个类:SystemMessageAssistantMessageUserMessageChatMessageFunctionMessage都是AbstractMessage抽象类的子类。
image.png

Message实现类的子类的名字不难看出,他们最主要的功能就是为发送给 AI 模型的消息赋予相应的角色。官方文档的说明如下:

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

主要的角色包括:

  • System:引导AI的行为和响应方式,为AI如何解释和回复输入设定参数或规则。这类似于在开始对话前向AI提供指示。
  • User:代表用户的输入——他们对AI的问题、命令或陈述。这一角色是基础性的,因为它构成了AI响应的基础。
  • Assistant:AI对用户输入的响应。它不仅仅是答案或反应,对于维持对话的流畅性至关重要。通过跟踪AI之前的响应(其“助手角色”消息),系统确保了连贯且上下文相关的互动。
  • Function:这一角色涉及对话过程中的特定任务或操作。当系统角色设定了AI的整体行为时,功能角色专注于执行用户请求的某些动作或命令。它就像AI中的一项特殊功能,在需要执行特定功能(如计算、获取数据或其他超出简单对话的任务)时使用。这一角色使AI除了提供对话响应外,还能提供实际帮助。

PromptTemplate

速览

前面的很,整点高级的😁,好叭其实也没有很高级😂

PromptTemplate 可以类比到JDBCTemplate,是一个用于提示模板的关键组件,具有AssistantSystemFunction三种角色对应的子类。以下是类的声明:

public class PromptTemplate implements PromptTemplateActions, PromptTemplateMessageActions {
    ......
}

解释一下它实现的两个接口的主要作用:

  • PromptTemplateMessageActions:专门针对通过生成和操作 Message 对象来创建提示。
  • PromptTemplateActions:设计用于返回 Prompt 对象,该对象可以传递给 ChatClient 生成响应。

好,那么好,咋用?

使用 PromptTemplate 最终的目的在于构建 Prompt 实例对象,这通过调用create方法来实现。根据官方的描述,用法主要有三种:通过在 raw String 中用{}设置占位符、通过 Resource,最终都经过 Map。
方式一示例:

String userText = """
    你是{name}的真爱粉,现在请你来一段{action}。
""";

// 假设已经通过参数传入了 name 和 action 这两个变量,
// name = "坤坤",action = "唱跳Rap打篮球"
PromptTemplate promptTemplate = new PromptTemplate(userText);
Prompt prompt = promptTemplate.create(Map.of(
    "name", name,
    "action", action
));

方式二示例:
首先在 Resource 目录下定义好 Prompt 文本文件,使用.st格式的结构化文本。这里就不唱跳 Rap 打篮球了,用鱼皮的星球项目—智能 BI 系统的 Prompt 来改造:

不了解的兄弟们戳:

```Special Attention
只生成指定格式的js代码,不要生成其他任何多余的开头、结尾、注释等内容
你是一个数据分析师和前端开发专家,接下来我会提供给你csv格式的原始数据,请对数据进行分析后,
生成前端 Echarts V5 的 option 配置对象js代码,合理地将数据进行可视化,
不要生成任何多余的内容,比如注释!!!
{data}

用三个撇号包括的原因在于,实践中我发现这样可以很大程度上保证 AI 模型返回内容的稳定性,即输出一般也都是三个撇号包裹的,我们后续处理会方便很多。而且分不同的片段,让 AI 能够充分理解每一个部分。

接下来就是在 Java 代码中加载这个 Resource:

@Value("classpath:/prompts/analysis-data.st")
private Resource analysisDataPrompt;

...

var template = new PromptTemplate(analysisDataPrompt);
var prompt = template.create(Map.of(
    "data", csvData	// 假设已经处理得到了一个csvData
));

小结

本文介绍了在 Spring AI 框架中使用 Prompt 的基本概念和实践方法,着重探讨了如何利用 Prompt 相关API与AI模型进行交互,构建高效且富有目的性的对话。

  1. 简单了解Prompt
    • Prompt:引导AI模型产生特定输出的输入信息,其设计直接影响模型响应。任何发给AI的信息均可视为Prompt。
    • Prompt的发展:从简单的字符串演变为包含特定输入占位符的结构,AI能识别并据此作出相应反馈。现在,Prompt工程已成为一门专门学科。
  2. API速览
    • Prompt类:作为与AI模型交互的主要工具类,封装了用户发送给模型的消息。构造函数支持多种方式创建Prompt对象,包括基于字符串、单个Message对象、Message列表,以及结合ChatOptions对象以提供更多模型交互选项。
    • Message接口:定义了消息的基本结构,包括内容、媒体、属性和消息类型。子类如UserMessage、AssistantMessage等,分别代表用户输入、AI响应等不同角色,增强了交互的上下文理解。
  3. PromptTemplate的运用
    • PromptTemplate类:类似JDBCTemplate,用于创建和管理Prompt模板,通过动态占位符、资源文件或Map数据填充内容,便于生成多样化的Prompt实例。
    • 创建Prompt实例:示例代码展示了如何使用PromptTemplate结合Map参数,动态替换模板中的占位符,以适应具体场景需求,如通过用户定义的name和action生成特定对话内容。
  • 15
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值