2714 字
约 9 分钟
5
Spring AI 提示词工程实践:从 Prompt 设计到统一 LLM 调用框

Spring AI 提示词工程实践:从 Prompt 设计到统一 LLM 调用框架

本文以求职派为例,梳理 Spring AI 的 Prompt、Message、PromptTemplate 核心机制,以及如何通过 LlmCaller 封装、模型路由和上下文管理,构建一套可复用的提示词工程体系。

一、为什么需要提示词工程?

最简单的大模型调用通常是:

Java

chatModel.call("帮我推荐 Java 岗位");

这种方式适合 Demo,但实际业务会遇到很多问题:

  • 模型不知道自己的业务角色。

  • 不同用户需要个性化回答。

  • 多轮对话容易丢失上下文。

  • 不同 Agent 重复编写模型调用逻辑。

  • 历史消息过长,导致 Token 消耗增加。

因此,需要将简单的模型调用升级成一套完整的提示词管理机制。

核心思想:将提示词构建、模型调用、上下文管理从业务代码中抽离,统一封装。

二、Spring AI 的三层提示词抽象

Spring AI 主要通过 Prompt、Message 和 PromptTemplate 组织模型请求。

1. Message:定义消息角色

Message 表示一次对话中的单条消息。

| 类型

|

作用

|

SystemMessage

|

定义模型角色与行为规则

| |

UserMessage

|

承载用户输入、图片等内容

| |

AssistantMessage

|

记录模型生成的回复

| |

ToolResponseMessage

|

承载工具执行结果

|

例如:

Java

Prompt prompt = new Prompt(
    List.of(
        new SystemMessage("你是一名 Java 技术面试官"),
        new UserMessage("请解释线程池的工作原理")
    )
);

SystemMessage 用于描述模型的职责,UserMessage 用于承载具体任务。

在 Agent 系统中,还会加入历史消息和工具交互记录,构成完整的对话上下文。

2. Prompt:封装完整模型请求

Prompt 不只是一段文本,而是:

Prompt
 ├── Messages
 │    ├── SystemMessage
 │    ├── UserMessage
 │    ├── AssistantMessage
 │    └── ToolResponseMessage
 │
 └── ChatOptions
      ├── Model
      └── Temperature

其中:

  • Messages 决定向模型提供哪些信息。

  • ChatOptions 决定使用哪个模型及其调用参数。

因此,Prompt 可以理解为一次模型调用的完整请求对象。

3. PromptTemplate:动态生成提示词

实际项目中,提示词通常包含动态变量。

例如:

Java

PromptTemplate template = new PromptTemplate(
    "你是一名{role},请帮助用户完成{task}"
);

Prompt prompt = template.create(Map.of(
    "role", "Java面试官",
    "task", "模拟技术面试"
));

通过模板复用,可以避免在业务代码中反复拼接相似的提示词。

需要注意,模板生成的消息角色取决于使用的模板类型和构建方式;系统提示词可以使用 SystemPromptTemplate

三、提示词工程化:模板外置

当业务提示词越来越复杂时,直接写在 Java 代码中会出现维护困难的问题。

例如,用户画像提取可能需要:

  • 角色定义。

  • 字段说明。

  • 输出格式约束。

  • 增量更新规则。

  • Few-shot 示例。

一份提示词可能达到数百行。

因此,求职派将提示词保存为独立的 Markdown 文件。

resources/
└── prompts/
    ├── identity-extraction.md
    ├── job-extraction.md
    ├── conversation-summary.md
    └── job-recommend.md

通过 Spring Resource 加载:

Java

@Value("classpath:/prompts/identity-extraction.md")
private Resource promptResource;

调用时注入动态变量:

Java

String prompt = template
    .replace("{current_soul}", identity)
    .replace("{conversation_history}", history);

这种设计的优势是:

  1. 提示词与业务代码分离。

  2. 不同 Agent 独立维护提示词。

  3. 提示词可以通过 Git 进行版本管理。

  4. 调整 Prompt 不需要修改 Java 业务逻辑。

不过,String.replace() 适合变量少、结构简单的模板。复杂模板更适合使用正式模板引擎,并处理好变量转义。

沉淀:Prompt 应当作为独立的工程资源进行管理,而不是散落在业务代码中的字符串。

四、统一 LLM 调用框架设计

假设系统有三个 Agent:

ResumeAgent
JobRecommendAgent
InterviewAgent

如果每个 Agent 都直接调用 chatModel.call(),就会出现大量重复逻辑:

选择模型
构造 Prompt
加载 Memory
注入用户画像
注册 Tools
执行模型调用

因此,求职派引入统一调用器 LlmCaller

Java

public interface LlmCaller {

    String call(
        UserConversationInfo user,
        Prompt prompt
    );

    Flux<String> stream(
        UserConversationInfo user,
        Prompt prompt
    );
}

通过接口统一同步与流式调用入口。

1. 分层调用器设计

根据业务复杂度划分不同等级的调用器。

BasicLlmCaller

基础模型调用

BizAgentLlmCaller

模型调用 + Memory + ReAct

FullLlmCaller

用户画像 + 工具 + MCP + 文件系统

不同业务选择不同的调用器:

| 业务

|

调用器

|

简单文本提取

|

Basic

| |

多轮业务对话

|

Business

| |

复杂工具型 Agent

|

Full

|

这种设计遵循最小能力原则:简单任务不加载无关工具,避免增加上下文开销与安全风险。

2. 模型自动路由

调用器还负责根据用户输入选择模型。

例如:

Java

boolean hasImage = prompt.getUserMessages()
    .stream()
    .anyMatch(m -> !m.getMedia().isEmpty());

if (hasImage) {
    model = visionModel;
}

具体实现需要做好空值处理和模型能力校验。

整体流程:

用户消息
   ↓
检查是否包含图片
   ↓
ModelRouter
   ├── 文本 → Text Model
   └── 图片 → Vision Model
   ↓
执行模型调用

Agent 无须了解每个模型的具体实现,只需要调用统一接口。

沉淀:通过调用器屏蔽模型选择、上下文处理和工具注册等基础设施细节。

五、上下文管理:解决长对话问题

大模型的上下文窗口有限。

随着对话轮数增加:

历史消息增长
      ↓
Token 消耗增加
      ↓
超过上下文预算
      ↓
调用失败或无法保留全部历史

求职派采用多级上下文管理机制。

1. 过滤低信息量消息

例如:

好的
嗯
收到
OK

这类消息可能对后续对话帮助较小,可以在特定场景下过滤。

但不能简单删除所有短消息。

例如:

用户:确定要投递这家公司吗?

用户下一轮:确定。

这里的「确定」就具有重要业务意义。

因此,过滤策略应保留关键确认、决策和工具交互记录。

2. 基于 Token 预算裁剪

相比只按照消息数量截断历史,Token 预算更贴近模型实际限制。

读取历史消息
     ↓
估算 Token
     ↓
是否超过预算?
     ↓ 是
裁剪较早消息
     ↓
保留最近对话

需要注意,中文字符数与 Token 数量不存在固定换算关系。

可以使用近似估算做初步控制,但应结合实际模型的 Token 统计能力,并预留输出预算及工具调用开销。

3. 会话摘要

当历史对话过长时,可以让模型将较早的消息压缩成摘要。

完整历史对话
      ↓
LLM 生成摘要
      ↓
保留关键事实
      ↓
摘要 + 最近消息
      ↓
构建新 Prompt

例如:

用户画像:
3年 Java 开发经验

求职意向:
杭州,后端开发

薪资要求:
20K 以上

此前已推荐:
A 公司、B 公司

摘要可以减少上下文占用,但并不能保证无损保留全部信息。

对于用户明确提出的要求、关键决策、任务状态等信息,应单独结构化保存,避免摘要遗漏。

六、完整的 LLM 调用链路

将上述能力组合起来,可以形成一条统一调用链。

用户输入
    ↓
获取用户身份与画像
    ↓
加载 Agent Prompt 模板
    ↓
加载历史消息
    ↓
上下文窗口管理
    ↓
组装 Prompt
    ↓
LlmCaller
    ├── ModelRouter
    ├── Memory
    └── Tools / MCP
    ↓
Spring AI
    ↓
LLM
    ↓
返回结果
    ↓
更新 Memory
    ↓
返回用户

这里需要明确模块职责:

| 模块

|

职责

|

PromptTemplate

|

管理可复用提示词

| |

Prompt

|

组织一次模型请求

| |

LlmCaller

|

统一执行模型调用

| |

ModelRouter

|

选择具体模型

| |

ChatMemory

|

保存对话历史

| |

ContextManager

|

控制上下文预算

|

它们共同形成 Agent 系统的模型调用基础设施。

七、生产环境还需要关注什么?

除了基本功能,提示词工程还需要关注可靠性和安全性。

| 问题

|

处理思路

|

Prompt 越来越复杂

|

模板外置、版本管理

| |

模型输出不稳定

|

结构化输出、校验、测试

| |

历史消息过长

|

Token 预算、裁剪、摘要

| |

用户画像错误

|

支持用户修改、定期更新

| |

Prompt Injection

|

区分可信指令与外部输入

| |

工具调用越权

|

独立鉴权、最小权限原则

|

特别需要注意:用户画像、网页内容和历史对话都不能被视为可信的系统指令。即使这些内容被注入系统提示词,也不能让其中的恶意指令获得更高权限。

Prompt 负责指导模型行为,但真正的权限控制必须由业务程序执行。

八、总结

Spring AI 提示词工程可以归纳为四个阶段。

| 阶段

|

核心能力

|

基础阶段

|

Prompt + Message

| |

模板阶段

|

PromptTemplate + Markdown 外置

| |

框架阶段

|

LlmCaller + ModelRouter

| |

工程阶段

|

Memory + 上下文窗口 + 会话摘要

|

整个演进过程是:

简单 Prompt
     ↓
角色与模板管理
     ↓
统一模型调用
     ↓
多模型和多模态适配
     ↓
上下文与记忆管理
     ↓
可复用的 Agent 调用框架

最终沉淀的架构思想:

Prompt 负责描述任务,Message 负责组织上下文,LlmCaller 负责统一调用,Memory 负责保存历史,ContextManager 负责控制上下文预算。

通过职责拆分,业务 Agent 只需要关注自身的 Prompt、工具和业务逻辑,而不需要重复处理底层模型调用细节。

这就是从简单 Spring AI Demo 走向可维护、多 Agent 系统的重要一步。

Spring AI 提示词工程实践:从 Prompt 设计到统一 LLM 调用框
http://www.clxhxhhr.top/posts/654/
作者
clxstart
发布于
2026-09-17
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。