提示词 (Prompt)

引言

在生成式 AI 应用中,Prompt(提示)是与大型语言模型(LLM)交互的核心输入格式。Prompt 的设计不仅决定了模型理解任务的准确度,还直接影响生成结果的风格、长度、结构与可控性。随着模型能力和应用场景的不断丰富,Prompt 的结构也从最初的简单文本演进为支持多角色区分、占位符模板化、工具调用等高级特性。本文将系统梳理 Prompt 的演变历程、角色(Role)分类及职责、Spring AI 中 Prompt API 的详细使用方法,以及 PromptTemplate 的模板化实践,并在每个小节提供详尽的文字说明和代码示例。


一、Prompt 的演变历程

Prompt 的演进大致可分为三个阶段:

1.1 初始阶段:简单字符串

背景与特点
  • 实现方式:直接将用户意图用自然语言表述为一条字符串,传入模型。

  • 示例

    Tell me a joke about cats.
    
    
  • 优点:上手门槛低,无需额外框架或结构。

  • 局限:无法提供上下文区分,也难以对输出进行精细控制,适用于单轮、简单的任务。

使用场景
  • 问答类 Chatbot 的一次性查询
  • 单条文本生成,如自动撰写短消息或标语

1.2 占位符与参数化

为什么要参数化?
  • 当同一类任务需要多次调用时,硬编码不同 Prompt 会导致维护成本高。
  • 参数化模板能够通过动态替换变量,提高 Prompt 的复用性和可维护性。
基本示例
String template = "Tell me a {adjective} joke about {topic}.";
String adjective = "silly";
String topic = "robots";
// 使用简单替换实现参数化
String prompt = template
    .replace("{adjective}", adjective)
    .replace("{topic}", topic);
System.out.println(prompt);
// 输出:Tell me a silly joke about robots.

详细说明
  1. 模板定义:使用 {} 包裹的占位符表示可替换的变量位置。
  2. 动态赋值:在运行时根据业务逻辑提供不同的变量值。
  3. 字符串替换:示例中使用 String.replace 实现,适合简单场景,但无法处理复杂数据类型(如列表、条件逻辑)。
参数化模板的优势
  • 高复用性:一份模板可支持多种输入组合。
  • 易于维护:模板与参数分离,修改模板无需改动调用代码。
  • 结构化:在模板中明确标识可变部分,增强可读性。

1.3 多角色消息流与上下文管理

角色化的必要性

随着对话式应用需求的增长,仅靠单条字符串难以维护多轮对话上下文,也无法区分系统指令与用户输入。引入角色(Role)后,每条消息都可携带角色标签,明确其在对话中的作用。

角色类型
  • SYSTEM:设定对话基调、行为规范或全局约束。
  • USER:真实用户的提问或指令。
  • ASSISTANT:模型的回复,用于记录对话历史和保持连贯性。
  • TOOL:外部工具或函数调用的输入/输出信息。
示例代码
List<Message> messages = new ArrayList<>();

// 系统角色:初始化对话规则
messages.add(new SystemMessage("You are a professional travel planner. Provide concise and accurate travel advice."));

// 用户角色:用户发起需求
messages.add(new UserMessage("I want to visit Xinjiang in autumn. What do you recommend?"));

// 模型角色:占位符,由模型生成后再添加到列表
// messages.add(new AssistantMessage(modelOutput));

// 下一轮用户角色:继续提问
// messages.add(new UserMessage("What is the weather forecast there for the next 3 days?"));

详细说明
  1. SystemMessage:通过 new SystemMessage(...) 创建,内容为对模型的全局指令。
  2. UserMessage:通过 new UserMessage(...) 创建,内容为用户输入。
  3. AssistantMessage:模型生成的回复,由程序接收并添加,用于多轮对话。
  4. Role 的作用:模型在生成回复时会根据角色区分系统指令与用户对话,确保响应符合预期。

二、Prompt 中的角色(Role)详解

在 Spring AI 中,MessageType 枚举将角色明确定义为四种类型:

public enum MessageType {
  SYSTEM("system"),   // 系统指令
  USER("user"),       // 用户输入
  ASSISTANT("assistant"), // 模型回复
  TOOL("tool");       // 工具调用
}

2.1 系统角色(System Role)

  • 主要职责:向模型提供身份设定、业务规则或回答风格。

  • 典型指令

    • “You are a helpful assistant that speaks formally.”
    • “Only answer questions related to legal advice.”
代码示例
Message system = new SystemMessage(
    "You are a financial advisor. Provide concise and accurate investment suggestions in JSON format."
);

扩展说明
  • 可以通过添加元数据(metadata)为系统消息携带额外信息,如时间戳或优先级。
  • 多个 SystemMessage 可按顺序添加,以分阶段设定对话规则。

2.2 用户角色(User Role)

  • 主要职责:传达用户的具体需求或问题。
  • 设计要点:用户消息应清晰、简洁,避免含糊不清的描述。
代码示例
Message user = new UserMessage(
    "Please recommend three budget-friendly hotels in Tokyo for a family of four."
);

扩展说明
  • 可在用户消息中列出多步操作,模型会尝试按顺序执行。
  • 对于复杂查询,建议在一条消息中明确所有必要参数,如时间、人数、预算等。

2.3 助手角色(Assistant Role)

  • 主要职责:承载模型的回复内容,同时可触发工具调用。
  • 触发工具调用:当模型需要执行计算或外部操作时,会在 AssistantMessage 中返回 function_call 字段,表明后续需调用对应工具。
代码示例
// 模型生成的回复示例
Message assistant = new AssistantMessage(
    "The weather in Xinjiang for the next 3 days is:\nDay1: 18°C, sunny\nDay2: 20°C, partly cloudy\nDay3: 17°C, light rain"
);

扩展说明
  • 在接收到 AssistantMessage 后,程序应检查是否存在工具调用请求,并根据 tool_name 和 arguments 调用相应工具。
  • 回复中可包含结构化内容(如 JSON、表格等),便于后续解析。

2.4 工具/功能角色(Tool/Function Role)

  • 主要职责:传递外部工具执行结果,如 API 调用、数据库查询或数学运算。
  • 示例场景:天气查询、汇率转换、订单查询等。
代码示例
// 工具调用返回示例
Message tool = new ToolMessage(
    "{ \"location\": \"Xinjiang\", \"forecast\": [18,20,17] }"
);

扩展说明
  • ToolMessage 的 content 通常为 JSON 格式,程序解析后将结果注入下一次 Prompt。
  • 多个工具调用可串联使用,形成复杂工作流。

三、Spring AI 的 Prompt API 深入

Spring AI 提供了 Prompt 和 Message 等核心类,简化 Prompt 构建与调用流程。以下示例演示完整调用链路,并对关键方法进行详细解读。

3.1 构建 Prompt 实例

// 1. 导入依赖:确保已引入 spring-ai 相关库

// 2. 构建消息列表
List<Message> messages = new ArrayList<>();
messages.add(system);
messages.add(user);

// 3. 创建 Prompt 对象
Prompt prompt = new Prompt(messages)
    // 4. 可选:设置 ChatOptions,如温度、最大 Token
    .options(ChatOptions.builder()
        .temperature(0.5)
        .maxTokens(500)
        .build());

详细说明
  1. 消息列表初始化:使用 ArrayList 存储各角色消息,顺序决定 Prompt 的上下文顺序。
  2. Prompt 构造函数new Prompt(List<Message>) 将消息列表复制到内部。
  3. 链式调用:通过 options(...) 方法设置模型参数,如温度(temperature)控制随机性,maxTokens 控制最大生成长度。

3.2 调用 ChatModel

// 5. 使用 ChatModel 或 ChatClient 发起调用
ChatResponse response = chatModel.call(prompt);

// 6. 解析响应
String reply = response.getResult().getOutput().getContent();
System.out.println("Assistant: " + reply);

详细说明
  • ChatModel.call():接收 Prompt,发送给底层 LLM 服务,并返回 ChatResponse
  • ChatResponse:封装模型生成的所有输出,包括文本、工具调用请求、Usage 信息等。
  • getResult().getOutput().getContent():提取生成文本内容。

3.3 动态添加消息

在多轮对话中,可根据上一轮回复动态添加消息:

// 上一轮回复添加为 AssistantMessage
messages.add(new AssistantMessage(reply));

// 下一轮用户输入
messages.add(new UserMessage("Thank you. Can you also suggest local cuisines?"));

// 重新构建并调用 Prompt
Prompt nextPrompt = new Prompt(messages);
ChatResponse nextResp = chatModel.call(nextPrompt);

详细说明
  • 将模型回复添加到消息列表,保证上下文连贯性。
  • 新的用户消息与历史消息一起传入,模型能够参考整个对话历史。

四、PromptTemplate:高级模板化设计

当 Prompt 逻辑复杂、参数众多时,手动拼接字符串或管理消息列表容易出错。PromptTemplate 基于 StringTemplate 引擎提供了灵活的模板化方案。

public class PromptTemplate implements PromptTemplateActions, PromptTemplateMessageActions {
    private final String template;
    public PromptTemplate(String template) {
        this.template = template;
    }
    // render/create 方法由接口提供,具体由 StringTemplate 引擎实现
}

4.1 渲染为纯文本

// 无参数模板
PromptTemplate staticTpl = new PromptTemplate("List three facts about the moon.");
String staticPrompt = staticTpl.render();
// staticPrompt == "List three facts about the moon."

// 带参数模板
PromptTemplate paramTpl = new PromptTemplate("List three {adjective} facts about the {subject}.");
String dynamicPrompt = paramTpl.render(Map.of(
    "adjective", "interesting",
    "subject", "moon"
));
// dynamicPrompt == "List three interesting facts about the moon."

详细说明
  • render():直接输出模板内容,适用于静态 Prompt。
  • render(Map):根据键值对替换占位符,适用于简单参数化需求。

4.2 生成 Message 对象

// 仅文本消息
Message msg1 = paramTpl.createMessage(Map.of(
    "adjective", "funny",
    "subject", "penguins"
));
// msg1.getContent() -> "List three funny facts about the penguins."
// 默认角色为 USER,可通过重载指定其他角色

详细说明
  • createMessage(Map):将渲染后的文本封装为 UserMessage,简化消息构建流程。
  • 也可使用重载方法传入媒体列表(图片、音频等),扩展多模态能力。

4.3 生成完整 Prompt

// 创建带系统和用户消息的模板
String tpl = "<system>You are {role}.</system>" +
             "<user>Summarize the following text: {text}</user>";
PromptTemplate fullTpl = new PromptTemplate(tpl);

Map<String,Object> data = Map.of(
    "role", "an academic writer",
    "text", "Artificial intelligence is transforming industries..."
);

// 生成 Prompt 并设置选项
Prompt fullPrompt = fullTpl
    .create(data)
    .options(ChatOptions.builder().maxTokens(200).build());

ChatResponse fullResp = chatModel.call(fullPrompt);
System.out.println(fullResp.getResult().getOutput().getContent());

详细说明
  • 模板标签:使用 <system><user> 标签将渲染内容分配给不同角色。
  • create(Map):渲染并拆分消息为多条 Message,并封装为 Prompt
  • options(...):为该 Prompt 单独设置模型参数。

五、进阶用法与最佳实践

  1. 分层组织 Prompt

    • 将不同职责的消息(系统、用户、助手、工具)分层组织,保持清晰的调用链路。
    • 例如,先添加所有系统级指令,再添加用户级查询,最后再执行工具调用。
  2. 语义化占位符

    • 使用有意义的占位符名称,如 {userName}{startDate},提高模板可读性和可维护性。
  3. 模板管理

    • 将常用 PromptTemplate 存储在配置文件或数据库中,支持在线更新和版本控制。
    • 结合 CI/CD 流程,实现模板自动化测试与回滚。
  4. 工具调用预留

    • 在模板中预先定义工具调用点,如 Please fetch current weather via function_call.
    • 模型在生成时会自动触发 function_call,程序接收后执行相应工具。
  5. 性能与成本控制

    • 缓存静态 Prompt 或已渲染模板,减少重复渲染开销。
    • 控制消息列表长度,定期对历史对话进行摘要或清理,避免超出模型上下文窗口。
  6. 安全与合规

    • 对用户输入进行校验和清洗,避免注入恶意指令。
    • 记录 Prompt 与响应日志,确保可审计、可追溯。

六、总结

Prompt 设计是生成式 AI 应用的基础,直接影响模型的行为和输出质量。通过理解 Prompt 的演变历程、合理划分角色、熟练使用 Spring AI 提供的 Prompt API 与 PromptTemplate 工具,开发者可以构建出灵活、高效且易维护的对话系统。结合最佳实践,您将能够在各类业务场景中充分发挥 LLM 的潜力,为用户带来流畅、智能的交互体验。


本文示例基于 Spring AI 框架及 StringTemplate 引擎,旨在为开发者提供系统的 Prompt 设计指导。