首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >深入Spring AI:PromptTemplate从模板到高级技巧

深入Spring AI:PromptTemplate从模板到高级技巧

原创
作者头像
有一只柴犬
发布2025-12-15 21:21:53
发布2025-12-15 21:21:53
5320
举报
文章被收录于专栏:人工智能人工智能

1、前言

如果学到了这里,相信大部分人对Prompt并不陌生了。

在 Spring AI 的世界里,与强大的语言模型进行交互的基石便是 Prompt(提示语)。它不仅仅是你输入给 AI 的一段文本,更是你与智能对话的桥梁,是你唤醒模型潜能的关键指令。理解 Prompt 的本质、构建原则以及在 Spring AI 中的应用,是每一位希望有效利用 AI 能力的开发者必须掌握的核心技能。

2、Prompt

Prompt是指导AI 模型生成特定输出的输入,一个有效的 Prompt 并非简单的提问,而是指令与上下文的巧妙融合。它告诉模型你期望它做什么(指令),并为其提供完成任务所需的背景信息(上下文)。模型接收到 Prompt 后,会基于其庞大的知识库和对语言的理解,生成符合你指令和上下文的输出。

通常我们使用Prompt会在ChatClient调用call方法时,传入一个prompt然后得到相应的ChatResponse返回。如我们前面使用到的:

代码语言:java
复制
UserMessage userMessage = new UserMessage(text);
String content = chatClient.prompt(new Prompt(userMessage)).call().content();

Prompt 类其实本身并没有什么太多的内容。从上面示例代码中我们可以看出Prompt其实只是封装了一系列Message,而真正传入给AI的提示词其实是封装在了UserMessage中。没错的,从org.springframework.ai.chat.prompt.Prompt源码我们也可以看出:

代码语言:java
复制
public class Prompt implements ModelRequest<List<Message>> {

    /**
     * 返回构成 Prompt 的消息列表。
     * 对于聊天模型,这通常包含用户消息、系统消息和助手消息。
     * 对于文本生成或嵌入模型,可能只有一个或多个用户消息。
     * @return 消息列表
     */
    private final List<Message> messages;

    /**
     * 返回与 Prompt 相关的选项。
     * 这些选项可能包含模型特定的参数,例如温度、最大 Token 数等。
     */
    @Nullable
    private ChatOptions chatOptions;

    ......

    /**
     * 返回 Prompt 的文本表示,这可能是将消息列表组合成单个字符串的结果。
     * 不同的模型可能需要不同格式的文本输入。
     * @return Prompt 的文本内容,如果没有则返回 null 或空字符串
     */
    public String getContents() {
       StringBuilder sb = new StringBuilder();
       for (Message message : getInstructions()) {
          sb.append(message.getText());
       }
       return sb.toString();
    }
}

在层次结构的顶层,对 LLM 的请求和响应由两个接口 ModelRequestModelResponse 表示。顾名思义, ModelRequest 表示发送到 AI 模型的请求, ModelResponse 表示收到的响应。

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

3、Message

Message其实是个接口,它通过集成Content接口定义了 文本内容、元数据属性的集合和称为 MessageType 的分类。

代码语言:java
复制
public interface Content {

    String getText();
    Map<String, Object> getMetadata();

}


public interface Message extends Content {
    // MessageType用来区分消息类型,如USER, SYSTEM,ASSISTANT等
    MessageType getMessageType();
}

官网给出的Spring AI Message 的类图:

SpringAI内置了几种Message的不同实现,用于表示不同角色的消息:

  • UserMessage: 代表用户的输入。
  • SystemMessage: 代表系统的指令或上下文设置。
  • AssistantMessage: 代表 AI 助手的回复(在对话历史中)。
  • ToolResponseMessage:代表函数或工具调用的结果。

4、PromptTemplate

Prompt的支持可以帮我们处理一些简单,无需动态生成内容的场景,或给到AI大模型之前,我们就已经通过其他方式构建好了输入。但是,当我们需要构建一些复杂的,比如根据用户输入,或根据数据库查询的数据需要动态生成Prompt对象。这个时候我们可能就需要额外做一些工作来封装Prompt,但是Spring AI也帮我们考虑好了,它提供了PromptTemplate的支持。

PromptTemplate实现了三个接口:

  • PromptTemplateActions:主要用于创建Prompt对象,该对象可直接传递给ChatClient以生成响应。
  • PromptTemplateStringActions: 主要用于创建和渲染提示词字符串,接口的返回值类型均是String类型,这是提示词的基本形式。
  • PromptTemplateMessageActions:主要用于创建Message对象,这允许我们针对Message对象进行其他的相关操作。

Spring AI 模块借助了由 Terence Parr 开发的第三方库 StringTemplate 引擎来构造和插值提示。这些子类进一步扩展了 PromptTemplate 类,但用法保持不变。

  • AssistantPromptTemplate:专门用于创建包含 助手消息 (AssistantMessage) 的 Prompt。助手消息代表 AI 模型之前的回复或输出。
  • FunctionPromptTemplate:专门用于创建与 函数调用 (FunctionCallMessage) 相关的 Prompt。FunctionCallMessage 用于指示 AI 模型需要调用一个预定义的函数。
  • SystemPromptTemplate:专门用于创建包含 系统消息 (SystemMessage) 的 Prompt。系统消息用于设置 AI 模型的行为、角色、目标或提供全局上下文。

三者的区别是:

特性

SystemPromptTemplate

AssistantPromptTemplate

FunctionPromptTemplate

消息类型

SystemMessage(设置模型行为和上下文)

AssistantMessage(模型之前的回复)

FunctionCallMessage(指示模型调用函数)

模板用途

定义模型的角色、目标、全局指令

记录和传递模型在对话中的历史回复

指示模型需要调用的函数名称和参数

应用场景

初始化对话上下文,设定模型行为准则

构建多轮对话,保持对话连贯性

触发 AI 模型调用外部功能或工具

模板内容示例

"你是一个专业的{领域}助手。"

"你之前的回答是:{previous_answer}。"

{"name": "{name}", "arguments": "{arguments}"}

4.1、简单使用

使用PromptTemplate也很简单:

代码语言:java
复制
@GetMapping("/promptTemplate")
public String promptTemplate() {
    // 使用系统消息预定好角色风格
    SystemPromptTemplate systemTemplate = new SystemPromptTemplate ("你是一位风格{style}的演说家");
    Message systemMessage = systemTemplate.createMessage(Map.of("style", "幽默诙谐"));

    PromptTemplate userTemplate = new PromptTemplate("请介绍一下自己的{store}");
    Message userMessage = userTemplate.createMessage(Map.of("store", "情感经历"));

    List<Message> messages = List.of(systemMessage, userMessage);
    String content = chatClient.prompt(new Prompt(messages)).call().content();
    System.out.println(content);
    return content;
}

如果有多个PromptTemplate需要组合,只需要将合并进一个List即可。

4.2、将模板字符串作为资源注入

4.1章节的示例代码,是将提示词写死在了Java代码中,而Spring AI允许将 prompt 数据放在 Resource 文件中,并直接注入到 PromptTemplate 中。在使用它之前,我们先来看一下他的核心源码实现,org.springframework.ai.chat.prompt.PromptTemplate

这里省略掉部分代码,只留下关键核心代码:

代码语言:java
复制
public class PromptTemplate implements PromptTemplateActions, PromptTemplateMessageActions {
    protected String template;
    protected TemplateFormat templateFormat = TemplateFormat.ST;
    private ST st;
    private Map<String, Object> dynamicModel = new HashMap<>();


    /**
     * 可以根据Resource 方式创建一个PromptTemplate对象
     * @param resource
     */
    public PromptTemplate(Resource resource) {
       // 将Resource资源文件中的数据读取出来
       try (InputStream inputStream = resource.getInputStream()) {
          this.template = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
       }
       catch (IOException ex) {
          throw new RuntimeException("Failed to read resource", ex);
       }
       try {
          // 构建StringTemplate实例
          this.st = new ST(this.template, '{', '}');
       }
       catch (Exception ex) {
          throw new IllegalArgumentException("The template string is not valid.", ex);
       }
    }


    /**
     * 可以根据Resource 方式创建一个PromptTemplate对象
     * @param template
     */
    public PromptTemplate(String template) {
       this.template = template;
       // If the template string is not valid, an exception will be thrown
       try {
          // 构建StringTemplate实例
          this.st = new ST(this.template, '{', '}');
       }
       catch (Exception ex) {
          throw new IllegalArgumentException("The template string is not valid.", ex);
       }
    }

    public PromptTemplate(String template, Map<String, Object> model) {
       this.template = template;
       // If the template string is not valid, an exception will be thrown
       try {
          this.st = new ST(this.template, '{', '}');
          for (Entry<String, Object> entry : model.entrySet()) {
             add(entry.getKey(), entry.getValue());
          }
       }
       catch (Exception ex) {
          throw new IllegalArgumentException("The template string is not valid.", ex);
       }
    }
    
    
    @Override
    public String render() {
       validate(this.dynamicModel);
       return this.st.render();
    }


    /**
     * 最终封装message时,会调用该方法进行渲染
     * @return
     */
    @Override
    public String render(Map<String, Object> model) {
        
       // 校验传入的变量,与占位符是否一致。如果不一致则不通过
       validate(model);
       for (Entry<String, Object> entry : model.entrySet()) {
          if (this.st.getAttribute(entry.getKey()) != null) {
             this.st.remove(entry.getKey());
          }
          // 读取资源文件
          if (entry.getValue() instanceof Resource) {
             this.st.add(entry.getKey(), renderResource((Resource) entry.getValue()));
          }
          else {
             this.st.add(entry.getKey(), entry.getValue());
          }

       }
       // 最终调用render生成提示词
       return this.st.render();
    }

    /**
     * promptTemplate生成提示词
     */
    @Override
    public Message createMessage(Map<String, Object> model) {
        return new UserMessage(render(model));
    }
    
    private String renderResource(Resource resource) {
       try {
          return resource.getContentAsString(Charset.defaultCharset());
       }
       catch (IOException e) {
          throw new RuntimeException(e);
       }
    }

    protected void validate(Map<String, Object> model) {

       Set<String> templateTokens = getInputVariables();
       Set<String> modelKeys = getModelKeys(model);

       // Check if model provides all keys required by the template
       if (!modelKeys.containsAll(templateTokens)) {
          templateTokens.removeAll(modelKeys);
          throw new IllegalStateException(
                "Not all template variables were replaced. Missing variable names are " + templateTokens);
       }
    }
}

当我们封装好的PromptTemplate调用createMessage生成提示词时,调用的就是createMessage() -> render() -> st.render() -> StringWriter -> write()。

4.2.1、简单使用

搞懂了他的渲染逻辑后,我们可以来尝试一下了。我们定义一份user-message.st文件,用来存放我们的提示词。

user-message.st:

代码语言:java
复制
讲两个冷笑话,笑话的主题分别是{subject1}和{subject2}

注入resource后,直接传给PromptTemplate:

代码语言:java
复制
@RestController
public class PromptTemplateWithSTController {

    @Value("classpath:/templates/user-message.st")
    private Resource promptUserMessage;

    private final ChatClient chatClient;
    public PromptTemplateWithSTController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    @GetMapping("/st")
    public String st() {
        PromptTemplate promptTemplate = new PromptTemplate(promptUserMessage);
        Prompt prompt = promptTemplate.create(Map.of("subject1", "程序员", "subject2", "打工人"));
        return chatClient.prompt(prompt)
                .call().content();
    }

}

显示结果:

其实感觉也没有那么玄乎。就是内置了一个字符串模板引擎生成对应的提示词。

5、Prompt Engineering

如果想要改善AI的结果,光知道Prompt的使用是不够的。生成好的一个提示词,直接影响AI的输出有效性和质量。Spring AI也给出了一些建议,用来帮助我们生成有效的Prompt:

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

  • 说明 :向 AI 提供清晰直接的指示,类似于您与人交流的方式。这种清晰度对于帮助 AI “理解” 预期内容至关重要。
  • 外部背景 :必要时包括 AI 响应的相关背景信息或具体指导。这个 “外部上下文” 构建了提示并帮助 AI 掌握整体场景。
  • 用户输入 :这是简单的部分 - 用户的直接请求或问题构成了提示的核心。
  • 输出指示器 : 这方面可能很棘手。它涉及为 AI 的响应指定所需的格式,例如 JSON。但是,请注意,AI 可能并不总是严格遵守此格式。例如,它可能会在实际 JSON 数据之前预置“here is your JSON”之类的短语,或者有时会生成不准确的类似 JSON 的结构。

5.1、创建有效的提示

以下是总结的一些常用的提示词技术:

1. Simple Techniques(简单技术)

  • a. 文本摘要 :量文本缩减为简洁的摘要,捕捉关键点和主要思想,同时省略不太关键的细节。
  • b. 问题解答 :侧重于根据用户提出的问题从提供的文本中得出特定答案。它是关于精确定位和提取相关信息以响应查询。
  • c. 文本分类 :系统地将文本分类为预定义的类别或组,分析文本并根据其内容将其分配到最合适的类别。
  • d. 对话 :创建交互式对话,AI 可以在其中与用户进行来回通信,模拟自然的对话流程。
  • e. 代码生成 :根据特定的用户要求或描述生成功能代码片段,将自然语言指令转换为可执行代码。

2. Advanced Techniques (高级技术)

  • a. 零样本 、 少数样本学习 :使模型能够做出准确的预测或响应,只需最少或没有特定问题类型的先前示例,即可使用学习的泛化理解新任务并采取行动。
  • b. 思路链:链接多个 AI 响应以创建连贯且上下文感知的对话。它帮助 AI 维护讨论的线索,确保相关性和连续性。
  • c. 反应 (原因 + 行为):在这种方法中,AI 首先分析(推理)输入,然后确定最合适的行动方案或响应。它将理解与决策相结合。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、前言
  • 2、Prompt
  • 3、Message
  • 4、PromptTemplate
    • 4.1、简单使用
    • 4.2、将模板字符串作为资源注入
      • 4.2.1、简单使用
  • 5、Prompt Engineering
    • 5.1、创建有效的提示
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档