求职派:深入拆解 Spring AI 提示词机制,附简历写法
本文目录 · 18 节
星球:Java进阶之路&二哥编程星球 | 专栏:求职派 OpenClaw 架构 | 作者:沉默王二 | 发布:2026-07-04
求职派:深入拆解 Spring AI 提示词机制,附简历写法
求职派:深入拆解 Spring AI 提示词机制,附简历写法
之前我们跑通了第一个 Spring AI 工程,大模型能正常回话了。
但把用户文本原封不动丢给模型,输出往往并不可靠。提示词是解决这些问题的关键钥匙,也是整个 Agent 系统的根基。
本篇内容第一版的作者:星球嘉宾灰灰。二哥做了二期的优化和迭代。
Spring AI 2.0 把提示词抽象成了 Prompt、Message、PromptTemplate 三层结构。包括身份注入处理、上下文管理、多模态消息、模型自动切换等。

这篇内容将从基础概念出发,逐步串联到求职派的真实源码,让大家搞清楚提示词是如何从 API 概念到生产落地。
01、Prompt 不只是一段文本
很多教程把提示词等同于“一段文本”。
在大模型的 HTTP API 层面,请求体里确实有个 messages 数组。但 Spring AI 把它抽象成了一个更丰富的对象,包含消息列表和调用参数。
Java
public class Prompt implements ModelRequest> {
private final List messages;
private ChatOptions chatOptions;
}
messages 是一个消息列表,每条消息绑定一个角色。chatOptions 控制模型调用的行为参数,比如温度、模型名称。

这个设计和 OpenAI API 的 messages 数组直接对应,Spring AI 用 Java 的 class 对象把 JSON 结构封装了一层。
创建 Prompt 最简单的方式是 new Prompt("给我讲个笑话"),Spring AI 自动把文本包装成一条用户消息。
但真实项目里几乎不会这么用,因为缺少系统指令,大模型不知道自己该扮演什么角色,输出质量全靠运气。如果想指定调用参数,就要传入第二个参数 ChatOptions。
Java
Prompt prompt = new Prompt(message,
ZhiPuAiChatOptions.builder()
.model("glm-5.2").temperature(0.7).build());
model 指定调用哪个模型,temperature 控制输出的随机性。
这两个参数几乎在每次调用时都会用到。搞清楚了 Prompt 的结构,下一个问题自然就来了,消息列表里的每条消息,角色是怎么分工的。
02、四种消息角色的分工
每条消息都有一个类型字段,和大模型定义的角色一一映射。

-
system 负责系统指令,告诉大模型“该扮演什么角色、遵守什么规则”。
-
user 承载用户输入,文本和图片都绑定到 user
-
assistant 标记模型生成的内容,由框架自动管理
-
tool 对应函数调用的返回结果,Agent 的 ReAct 循环里会用到
四个角色里,我们直接操控的是 system 和 user。来看一个直观的例子,给大模型设定一个“儿童聊天助手”身份。
Java
Prompt prompt = new Prompt(
List.of(new SystemMessage("你是专注于给3-5岁儿童聊天的助手"),
new UserMessage(message)),
ZhiPuAiChatOptions.builder()
.model("glm-5.2").temperature(0.7).build());
加上 SystemMessage 后,同样问“讲个笑话”,输出明显更适合小朋友。

temperature 设为 0.7 意味着要平衡确定性与多样性。
接近 0 时输出趋于保守,接近 1 时输出更有创造性但可能跑偏。实际业务场景里,问答类建议 0.3 左右,创意类可以调到 0.8。
求职派的业务 Agent 调用器在初始化时,会把用户画像和 Agent 指令拼接成一条系统消息。
Java
String identity = identityAgent.buildSoulPrompt(userId);
if (StringUtils.isNotBlank(identity)) {
sys = buildSystemPrompt(identity + "\n\n" + systemPrompt);
}
这段逻辑先从身份系统拿到用户画像(教育背景、技能、求职偏好),再和 Agent 自身的系统指令拼在一起。

同一个岗位的推荐 Agent,面对应届生和三年经验的开发者,会给出完全不同的推荐结果。系统消息不是写死的,而是跟着用户动态变化。
多模态场景下,用户消息还能携带图片和文件。
求职派从微信/钉钉/飞书收到的消息经过渠道适配后,文本和图片通过 UserMessage.builder() 统一装进一条用户消息(代码位置 core/.../BizAgentLlmCaller.java,第 203-209 行)。图片以 Media 对象的形式挂在 UserMessage 上,支持字节数组和文件路径两种来源。
Media 附件,决定了后面调文本模型还是视觉模型。
03、提示词模板
当提示词里有动态内容时,硬编码拼接会变得不可维护。Spring AI 提供了模板机制,用 {变量名} 做占位符。
Java
PromptTemplate tpl = new PromptTemplate(
"你来扮演{personality}的{aiRole}, 我来扮演{myRole}");
Prompt prompt = tpl.create(Map.of(
"personality", "温柔", "aiRole", "女朋友", "myRole", "男朋友"));
传入不同参数,大模型的表现截然不同。

默认 create() 生成的是用户消息类型。
如果要生成系统提示词,换成 SystemPromptTemplate 调 createMessage() 即可。
在求职派里,PromptTemplate 并不是主力。系统提示词会 把 .md 文件当模板,用 String.replace 做变量替换。
以身份提取器为例,启动时通过 @Value 注入 classpath 下的 .md 文件,调用时直接 replace 替换变量。
Java
@Value("classpath:/prompts/user-identity-extraction-prompt.md")
Resource promptResource;
this.promptTemplate = promptResource.getContentAsString(UTF_8);
// 调用时
String prompt = promptTemplate
.replace("{current_soul}", existingIdentity)
.replace("{conversation_history}", conversationText);
为什么不用 PromptTemplate?
看一眼身份提取器的模板文件就明白了。
这个 .md 文件接近 200 行,前面定义角色(“你是一个专业的用户画像分析助手”),中间规定输出格式(Markdown 结构,包含求职偏好、教育背景、技能清单等 10 多个字段),后面给出 2 个完整的 Few-shot 示例教模型怎么做增量更新。
整个模板里只有 2 个变量占位符: {current_soul} 和 {conversation_history}。
这种“变量极少但模板极长”的场景,PromptTemplate 的模板引擎优势发挥不出来,反而是 .md 文件 + String.replace 最直接。

模板放在 resources 目录下跟随代码一起进行版本管理,产品经理改一个输出字段不需要动 Java 代码,改完 .md 文件重启就生效。
求职派目前有 5 个以上的提示词模板文件分布在不同 Agent 模块的 resources/prompts/ 目录下,涵盖身份提取、灵魂画像、岗位信息抽取、会话摘要等场景。

每个 Agent 的提示词独立管理,互不干扰。
提示词外置到资源文件,是 Spring AI 项目从 demo 走向生产几乎必经的一步。
04、LLM 调用封装
直接在业务代码里写 chatModel.call(prompt) 有问题吗?
有。
至少三个,每个 Agent 都要自己处理模型选择、上下文记忆管理、多模态消息构建。求职派为此抽象了一层调用器。
Java
public interface LlmCaller {
String call(UserConversationInfo user, Prompt prompt);
Flux stream(UserConversationInfo user, Prompt prompt);
}
三个实现类层层递进。
-
轻量调用器,由调用者自己维护上下文,不带记忆也不注册工具。适合身份提取、会话摘要这类一次性调用,用完即走
-
业务 Agent 调用器,加了 ChatMemory 做对话持久化,接入自定义的 ReAct 推理循环,工具调用经过中间件拦截。大部分业务 Agent(岗位抓取、身份收集)用这一层
-
用户偏好调用器,在业务 Agent 基础上自动读取用户画像注入系统消息,同时注册文件系统、Shell、MCP、网页抓取等十余种工具。求职推荐 Agent 用的就是这个
每一层在上一层的基础上只增加必要的能力,业务代码按需选用,不必为简单场景付出复杂场景的开销。

这里面有这样一个巧妙的设计,模型自动切换。当用户消息包含图片时,系统自动从文本模型切到视觉模型。
Java
if (prompt.getUserMessages().stream()
.anyMatch(m -> !CollectionUtils.isEmpty(m.getMedia()))) {
model = ModelConfig.ModelType.VISION;
}
业务 Agent 完全不用关心“用户发了图片该调哪个模型”,调用器在底层自动判断。
这种把基础设施决策下沉到公共层的做法,在多 Agent 系统里能显著降低每个 Agent 的实现复杂度。
05、历史消息怎么管理?
多轮对话场景下,Prompt 的消息列表会不断增长,最终超出大模型的上下文窗口。
求职派用智能窗口管理器解决了这个问题,分三步。

第一步,过滤无用消息。正则匹配纯确认类回复,“好的”“嗯”“OK”“收到”这些不携带实际信息的消息直接过滤。
Java
Pattern.compile(
"^(好的|收到|嗯|哦|好|知道了|明白了|ok|OK|yes)$",
Pattern.CASE_INSENSITIVE);
只过滤用户消息,模型回复即使很短也保留,因为短回复可能是关键确认。
第二步,token 估算与裁剪。中文字符按每字 2 个 token 计算,英文单词按 1.3 个 token 计算,加上每条消息 10 个 token 的结构开销。
超出预算时从最旧的消息开始删除,但保留最近若干条不动。
第三步,总条数兜底。即使 token 没超,消息条数超过上限也会裁剪。
Spring AI 自带的 MessageWindowChatMemory 只支持按条数裁剪,不区分消息的信息密度。
一条“嗯”和一条 500 字的技术讨论在它眼里是等权的。
求职派在这个基础上做了两个改进,基于 token 的裁剪让预算控制更精确,无用消息过滤把有限的窗口留给真正有价值的内容。尤其在中文场景下,一个汉字约等于 2 个 token,如果不做区分估算,纯按英文假设计算会严重低估实际 token 消耗。
当对话更长时,求职派还有一层会话摘要机制作为补充。
超过一定轮数的历史消息会被 LLM 压缩成一段摘要,以一条新的消息注入回上下文,这样既不丢失早期对话的核心信息,又把 token 占用压到原来的十分之一左右。
窗口管理和会话摘要配合使用,保证了求职派在长对话场景下的稳定表现。
从提示词构建到上下文管理,整条线环环相扣——Prompt 承载消息和参数,Message 的角色决定信息的定位,模板让提示词可复用可维护,调用器在底层统一处理模型选择和身份注入,窗口管理器则确保历史消息不会撑爆上下文。
提示词是大模型交互的起点,也是最容易被低估的环节。
【把提示词当作工程问题来对待,大模型才会真正按我们想要的方式工作。】
06、如何把求职派写到简历上?
项目名称:求职派(JobClaw)多 Agent 求职系统
项目简介:基于 Spring AI 2.0 的多 Agent 求职平台,支持微信/钉钉/飞书多渠道接入,通过意图识别、Agent 路由、用户画像和上下文记忆,为用户提供身份采集、岗位抓取、岗位推荐和求职问答服务。
技术栈:Java 21、Spring Boot 4.0、Spring AI 2.0、LangGraph4J、Reactor、YAML、H2/MySQL
核心职责:
-
基于 Spring AI 的 Prompt 和 ChatClient 抽象,封装 LLM 调用器(轻量/业务/用户偏好),统一处理模型选择、流式响应、会话记忆持久化和工具传参。
-
将 9 个提示词模板外置为 Markdown 资源文件,覆盖身份提取、灵魂画像、岗位抽取、会话摘要等场景,提示词与 Java 代码解耦,支持多 Agent 独立迭代,产品侧改模板不需要动业务代码。
-
实现智能上下文窗口管理,通过低效消息过滤、中英文 token 估算将多轮对话历史控制在预算内;支持超长对话触发异步摘要压缩,每 10 轮刷新一次,旧消息压缩为 100 字以内的摘要并归档。
-
设计用户身份注入机制,按系统规则、性格画像、基础信息、求职偏好四层维护用户档案,自动从对话中增量提取并拼接到系统提示词,同一个推荐 Agent 面对不同用户给出差异化推荐结果。
-
基于自定义 ReAct 替换 Spring AI 内置的自动工具执行,注册文件系统、Shell、MCP、网页抓取等十余种工具,并在推理-行动中插入中间件 Hook 钩子,支持 Function Calling 的日志记录和行为审计。
之前我们跑通了第一个 Spring AI 工程,大模型能正常回话了。
但把用户文本原封不动丢给模型,输出往往并不可靠。提示词是解决这些问题的关键钥匙,也是整个 Agent 系统的根基。
本篇内容第一版的作者:星球嘉宾灰灰。二哥做了二期的优化和迭代。
Spring AI 2.0 把提示词抽象成了 Prompt、Message、PromptTemplate 三层结构。包括身份注入处理、上下文管理、多模态消息、模型自动切换等。

这篇内容将从基础概念出发,逐步串联到求职派的真实源码,让大家搞清楚提示词是如何从 API 概念到生产落地。
01、Prompt 不只是一段文本
很多教程把提示词等同于“一段文本”。
在大模型的 HTTP API 层面,请求体里确实有个 messages 数组。但 Spring AI 把它抽象成了一个更丰富的对象,包含消息列表和调用参数。
Java
public class Prompt implements ModelRequest> {
private final List messages;
private ChatOptions chatOptions;
}
messages 是一个消息列表,每条消息绑定一个角色。chatOptions 控制模型调用的行为参数,比如温度、模型名称。

这个设计和 OpenAI API 的 messages 数组直接对应,Spring AI 用 Java 的 class 对象把 JSON 结构封装了一层。
创建 Prompt 最简单的方式是 new Prompt("给我讲个笑话"),Spring AI 自动把文本包装成一条用户消息。
但真实项目里几乎不会这么用,因为缺少系统指令,大模型不知道自己该扮演什么角色,输出质量全靠运气。如果想指定调用参数,就要传入第二个参数 ChatOptions。
Java
Prompt prompt = new Prompt(message,
ZhiPuAiChatOptions.builder()
.model("glm-5.2").temperature(0.7).build());
model 指定调用哪个模型,temperature 控制输出的随机性。
这两个参数几乎在每次调用时都会用到。搞清楚了 Prompt 的结构,下一个问题自然就来了,消息列表里的每条消息,角色是怎么分工的。
02、四种消息角色的分工
每条消息都有一个类型字段,和大模型定义的角色一一映射。

-
system 负责系统指令,告诉大模型“该扮演什么角色、遵守什么规则”。
-
user 承载用户输入,文本和图片都绑定到 user
-
assistant 标记模型生成的内容,由框架自动管理
-
tool 对应函数调用的返回结果,Agent 的 ReAct 循环里会用到
四个角色里,我们直接操控的是 system 和 user。来看一个直观的例子,给大模型设定一个“儿童聊天助手”身份。
Java
Prompt prompt = new Prompt(
List.of(new SystemMessage("你是专注于给3-5岁儿童聊天的助手"),
new UserMessage(message)),
ZhiPuAiChatOptions.builder()
.model("glm-5.2").temperature(0.7).build());
加上 SystemMessage 后,同样问“讲个笑话”,输出明显更适合小朋友。

temperature 设为 0.7 意味着要平衡确定性与多样性。
接近 0 时输出趋于保守,接近 1 时输出更有创造性但可能跑偏。实际业务场景里,问答类建议 0.3 左右,创意类可以调到 0.8。
求职派的业务 Agent 调用器在初始化时,会把用户画像和 Agent 指令拼接成一条系统消息。
Java
String identity = identityAgent.buildSoulPrompt(userId);
if (StringUtils.isNotBlank(identity)) {
sys = buildSystemPrompt(identity + "\n\n" + systemPrompt);
}
这段逻辑先从身份系统拿到用户画像(教育背景、技能、求职偏好),再和 Agent 自身的系统指令拼在一起。

同一个岗位的推荐 Agent,面对应届生和三年经验的开发者,会给出完全不同的推荐结果。系统消息不是写死的,而是跟着用户动态变化。
多模态场景下,用户消息还能携带图片和文件。
求职派从微信/钉钉/飞书收到的消息经过渠道适配后,文本和图片通过 UserMessage.builder() 统一装进一条用户消息(代码位置 core/.../BizAgentLlmCaller.java,第 203-209 行)。图片以 Media 对象的形式挂在 UserMessage 上,支持字节数组和文件路径两种来源。
Media 附件,决定了后面调文本模型还是视觉模型。
03、提示词模板
当提示词里有动态内容时,硬编码拼接会变得不可维护。Spring AI 提供了模板机制,用 {变量名} 做占位符。
Java
PromptTemplate tpl = new PromptTemplate(
"你来扮演{personality}的{aiRole}, 我来扮演{myRole}");
Prompt prompt = tpl.create(Map.of(
"personality", "温柔", "aiRole", "女朋友", "myRole", "男朋友"));
传入不同参数,大模型的表现截然不同。

默认 create() 生成的是用户消息类型。
如果要生成系统提示词,换成 SystemPromptTemplate 调 createMessage() 即可。
在求职派里,PromptTemplate 并不是主力。系统提示词会 把 .md 文件当模板,用 String.replace 做变量替换。
以身份提取器为例,启动时通过 @Value 注入 classpath 下的 .md 文件,调用时直接 replace 替换变量。
Java
@Value("classpath:/prompts/user-identity-extraction-prompt.md")
Resource promptResource;
this.promptTemplate = promptResource.getContentAsString(UTF_8);
// 调用时
String prompt = promptTemplate
.replace("{current_soul}", existingIdentity)
.replace("{conversation_history}", conversationText);
为什么不用 PromptTemplate?
看一眼身份提取器的模板文件就明白了。
这个 .md 文件接近 200 行,前面定义角色(“你是一个专业的用户画像分析助手”),中间规定输出格式(Markdown 结构,包含求职偏好、教育背景、技能清单等 10 多个字段),后面给出 2 个完整的 Few-shot 示例教模型怎么做增量更新。
整个模板里只有 2 个变量占位符: {current_soul} 和 {conversation_history}。
这种“变量极少但模板极长”的场景,PromptTemplate 的模板引擎优势发挥不出来,反而是 .md 文件 + String.replace 最直接。

模板放在 resources 目录下跟随代码一起进行版本管理,产品经理改一个输出字段不需要动 Java 代码,改完 .md 文件重启就生效。
求职派目前有 5 个以上的提示词模板文件分布在不同 Agent 模块的 resources/prompts/ 目录下,涵盖身份提取、灵魂画像、岗位信息抽取、会话摘要等场景。

每个 Agent 的提示词独立管理,互不干扰。
提示词外置到资源文件,是 Spring AI 项目从 demo 走向生产几乎必经的一步。
04、LLM 调用封装
直接在业务代码里写 chatModel.call(prompt) 有问题吗?
有。
至少三个,每个 Agent 都要自己处理模型选择、上下文记忆管理、多模态消息构建。求职派为此抽象了一层调用器。
Java
public interface LlmCaller {
String call(UserConversationInfo user, Prompt prompt);
Flux stream(UserConversationInfo user, Prompt prompt);
}
三个实现类层层递进。
-
轻量调用器,由调用者自己维护上下文,不带记忆也不注册工具。适合身份提取、会话摘要这类一次性调用,用完即走
-
业务 Agent 调用器,加了 ChatMemory 做对话持久化,接入自定义的 ReAct 推理循环,工具调用经过中间件拦截。大部分业务 Agent(岗位抓取、身份收集)用这一层
-
用户偏好调用器,在业务 Agent 基础上自动读取用户画像注入系统消息,同时注册文件系统、Shell、MCP、网页抓取等十余种工具。求职推荐 Agent 用的就是这个
每一层在上一层的基础上只增加必要的能力,业务代码按需选用,不必为简单场景付出复杂场景的开销。

这里面有这样一个巧妙的设计,模型自动切换。当用户消息包含图片时,系统自动从文本模型切到视觉模型。
Java
if (prompt.getUserMessages().stream()
.anyMatch(m -> !CollectionUtils.isEmpty(m.getMedia()))) {
model = ModelConfig.ModelType.VISION;
}
业务 Agent 完全不用关心“用户发了图片该调哪个模型”,调用器在底层自动判断。
这种把基础设施决策下沉到公共层的做法,在多 Agent 系统里能显著降低每个 Agent 的实现复杂度。
05、历史消息怎么管理?
多轮对话场景下,Prompt 的消息列表会不断增长,最终超出大模型的上下文窗口。
求职派用智能窗口管理器解决了这个问题,分三步。

第一步,过滤无用消息。正则匹配纯确认类回复,“好的”“嗯”“OK”“收到”这些不携带实际信息的消息直接过滤。
Java
Pattern.compile(
"^(好的|收到|嗯|哦|好|知道了|明白了|ok|OK|yes)$",
Pattern.CASE_INSENSITIVE);
只过滤用户消息,模型回复即使很短也保留,因为短回复可能是关键确认。
第二步,token 估算与裁剪。中文字符按每字 2 个 token 计算,英文单词按 1.3 个 token 计算,加上每条消息 10 个 token 的结构开销。
超出预算时从最旧的消息开始删除,但保留最近若干条不动。
第三步,总条数兜底。即使 token 没超,消息条数超过上限也会裁剪。
Spring AI 自带的 MessageWindowChatMemory 只支持按条数裁剪,不区分消息的信息密度。
一条“嗯”和一条 500 字的技术讨论在它眼里是等权的。
求职派在这个基础上做了两个改进,基于 token 的裁剪让预算控制更精确,无用消息过滤把有限的窗口留给真正有价值的内容。尤其在中文场景下,一个汉字约等于 2 个 token,如果不做区分估算,纯按英文假设计算会严重低估实际 token 消耗。
当对话更长时,求职派还有一层会话摘要机制作为补充。
超过一定轮数的历史消息会被 LLM 压缩成一段摘要,以一条新的消息注入回上下文,这样既不丢失早期对话的核心信息,又把 token 占用压到原来的十分之一左右。
窗口管理和会话摘要配合使用,保证了求职派在长对话场景下的稳定表现。
从提示词构建到上下文管理,整条线环环相扣——Prompt 承载消息和参数,Message 的角色决定信息的定位,模板让提示词可复用可维护,调用器在底层统一处理模型选择和身份注入,窗口管理器则确保历史消息不会撑爆上下文。
提示词是大模型交互的起点,也是最容易被低估的环节。
【把提示词当作工程问题来对待,大模型才会真正按我们想要的方式工作。】
06、如何把求职派写到简历上?
项目名称:求职派(JobClaw)多 Agent 求职系统
项目简介:基于 Spring AI 2.0 的多 Agent 求职平台,支持微信/钉钉/飞书多渠道接入,通过意图识别、Agent 路由、用户画像和上下文记忆,为用户提供身份采集、岗位抓取、岗位推荐和求职问答服务。
技术栈:Java 21、Spring Boot 4.0、Spring AI 2.0、LangGraph4J、Reactor、YAML、H2/MySQL
核心职责:
-
基于 Spring AI 的 Prompt 和 ChatClient 抽象,封装 LLM 调用器(轻量/业务/用户偏好),统一处理模型选择、流式响应、会话记忆持久化和工具传参。
-
将 9 个提示词模板外置为 Markdown 资源文件,覆盖身份提取、灵魂画像、岗位抽取、会话摘要等场景,提示词与 Java 代码解耦,支持多 Agent 独立迭代,产品侧改模板不需要动业务代码。
-
实现智能上下文窗口管理,通过低效消息过滤、中英文 token 估算将多轮对话历史控制在预算内;支持超长对话触发异步摘要压缩,每 10 轮刷新一次,旧消息压缩为 100 字以内的摘要并归档。
-
设计用户身份注入机制,按系统规则、性格画像、基础信息、求职偏好四层维护用户档案,自动从对话中增量提取并拼接到系统提示词,同一个推荐 Agent 面对不同用户给出差异化推荐结果。
-
基于自定义 ReAct 替换 Spring AI 内置的自动工具执行,注册文件系统、Shell、MCP、网页抓取等十余种工具,并在推理-行动中插入中间件 Hook 钩子,支持 Function Calling 的日志记录和行为审计。
之前我们跑通了第一个 Spring AI 工程,大模型能正常回话了。
但把用户文本原封不动丢给模型,输出往往并不可靠。提示词是解决这些问题的关键钥匙,也是整个 Agent 系统的根基。
本篇内容第一版的作者:星球嘉宾灰灰。二哥做了二期的优化和迭代。
Spring AI 2.0 把提示词抽象成了 Prompt、Message、PromptTemplate 三层结构。包括身份注入处理、上下文管理、多模态消息、模型自动切换等。

这篇内容将从基础概念出发,逐步串联到求职派的真实源码,让大家搞清楚提示词是如何从 API 概念到生产落地。
01、Prompt 不只是一段文本
很多教程把提示词等同于“一段文本”。
在大模型的 HTTP API 层面,请求体里确实有个 messages 数组。但 Spring AI 把它抽象成了一个更丰富的对象,包含消息列表和调用参数。
Java
public class Prompt implements ModelRequest> {
private final List messages;
private ChatOptions chatOptions;
}
messages 是一个消息列表,每条消息绑定一个角色。chatOptions 控制模型调用的行为参数,比如温度、模型名称。

这个设计和 OpenAI API 的 messages 数组直接对应,Spring AI 用 Java 的 class 对象把 JSON 结构封装了一层。
创建 Prompt 最简单的方式是 new Prompt("给我讲个笑话"),Spring AI 自动把文本包装成一条用户消息。
但真实项目里几乎不会这么用,因为缺少系统指令,大模型不知道自己该扮演什么角色,输出质量全靠运气。如果想指定调用参数,就要传入第二个参数 ChatOptions。
Java
Prompt prompt = new Prompt(message,
ZhiPuAiChatOptions.builder()
.model("glm-5.2").temperature(0.7).build());
model 指定调用哪个模型,temperature 控制输出的随机性。
这两个参数几乎在每次调用时都会用到。搞清楚了 Prompt 的结构,下一个问题自然就来了,消息列表里的每条消息,角色是怎么分工的。
02、四种消息角色的分工
每条消息都有一个类型字段,和大模型定义的角色一一映射。

-
system 负责系统指令,告诉大模型“该扮演什么角色、遵守什么规则”。
-
user 承载用户输入,文本和图片都绑定到 user
-
assistant 标记模型生成的内容,由框架自动管理
-
tool 对应函数调用的返回结果,Agent 的 ReAct 循环里会用到
四个角色里,我们直接操控的是 system 和 user。来看一个直观的例子,给大模型设定一个“儿童聊天助手”身份。
Java
Prompt prompt = new Prompt(
List.of(new SystemMessage("你是专注于给3-5岁儿童聊天的助手"),
new UserMessage(message)),
ZhiPuAiChatOptions.builder()
.model("glm-5.2").temperature(0.7).build());
加上 SystemMessage 后,同样问“讲个笑话”,输出明显更适合小朋友。

temperature 设为 0.7 意味着要平衡确定性与多样性。
接近 0 时输出趋于保守,接近 1 时输出更有创造性但可能跑偏。实际业务场景里,问答类建议 0.3 左右,创意类可以调到 0.8。
求职派的业务 Agent 调用器在初始化时,会把用户画像和 Agent 指令拼接成一条系统消息。
Java
String identity = identityAgent.buildSoulPrompt(userId);
if (StringUtils.isNotBlank(identity)) {
sys = buildSystemPrompt(identity + "\n\n" + systemPrompt);
}
这段逻辑先从身份系统拿到用户画像(教育背景、技能、求职偏好),再和 Agent 自身的系统指令拼在一起。

同一个岗位的推荐 Agent,面对应届生和三年经验的开发者,会给出完全不同的推荐结果。系统消息不是写死的,而是跟着用户动态变化。
多模态场景下,用户消息还能携带图片和文件。
求职派从微信/钉钉/飞书收到的消息经过渠道适配后,文本和图片通过 UserMessage.builder() 统一装进一条用户消息(代码位置 core/.../BizAgentLlmCaller.java,第 203-209 行)。图片以 Media 对象的形式挂在 UserMessage 上,支持字节数组和文件路径两种来源。
Media 附件,决定了后面调文本模型还是视觉模型。
03、提示词模板
当提示词里有动态内容时,硬编码拼接会变得不可维护。Spring AI 提供了模板机制,用 {变量名} 做占位符。
Java
PromptTemplate tpl = new PromptTemplate(
"你来扮演{personality}的{aiRole}, 我来扮演{myRole}");
Prompt prompt = tpl.create(Map.of(
"personality", "温柔", "aiRole", "女朋友", "myRole", "男朋友"));
传入不同参数,大模型的表现截然不同。

默认 create() 生成的是用户消息类型。
如果要生成系统提示词,换成 SystemPromptTemplate 调 createMessage() 即可。
在求职派里,PromptTemplate 并不是主力。系统提示词会 把 .md 文件当模板,用 String.replace 做变量替换。
以身份提取器为例,启动时通过 @Value 注入 classpath 下的 .md 文件,调用时直接 replace 替换变量。
Java
@Value("classpath:/prompts/user-identity-extraction-prompt.md")
Resource promptResource;
this.promptTemplate = promptResource.getContentAsString(UTF_8);
// 调用时
String prompt = promptTemplate
.replace("{current_soul}", existingIdentity)
.replace("{conversation_history}", conversationText);
为什么不用 PromptTemplate?
看一眼身份提取器的模板文件就明白了。
这个 .md 文件接近 200 行,前面定义角色(“你是一个专业的用户画像分析助手”),中间规定输出格式(Markdown 结构,包含求职偏好、教育背景、技能清单等 10 多个字段),后面给出 2 个完整的 Few-shot 示例教模型怎么做增量更新。
整个模板里只有 2 个变量占位符: {current_soul} 和 {conversation_history}。
这种“变量极少但模板极长”的场景,PromptTemplate 的模板引擎优势发挥不出来,反而是 .md 文件 + String.replace 最直接。

模板放在 resources 目录下跟随代码一起进行版本管理,产品经理改一个输出字段不需要动 Java 代码,改完 .md 文件重启就生效。
求职派目前有 5 个以上的提示词模板文件分布在不同 Agent 模块的 resources/prompts/ 目录下,涵盖身份提取、灵魂画像、岗位信息抽取、会话摘要等场景。

每个 Agent 的提示词独立管理,互不干扰。
提示词外置到资源文件,是 Spring AI 项目从 demo 走向生产几乎必经的一步。
04、LLM 调用封装
直接在业务代码里写 chatModel.call(prompt) 有问题吗?
有。
至少三个,每个 Agent 都要自己处理模型选择、上下文记忆管理、多模态消息构建。求职派为此抽象了一层调用器。
Java
public interface LlmCaller {
String call(UserConversationInfo user, Prompt prompt);
Flux stream(UserConversationInfo user, Prompt prompt);
}
三个实现类层层递进。
-
轻量调用器,由调用者自己维护上下文,不带记忆也不注册工具。适合身份提取、会话摘要这类一次性调用,用完即走
-
业务 Agent 调用器,加了 ChatMemory 做对话持久化,接入自定义的 ReAct 推理循环,工具调用经过中间件拦截。大部分业务 Agent(岗位抓取、身份收集)用这一层
-
用户偏好调用器,在业务 Agent 基础上自动读取用户画像注入系统消息,同时注册文件系统、Shell、MCP、网页抓取等十余种工具。求职推荐 Agent 用的就是这个
每一层在上一层的基础上只增加必要的能力,业务代码按需选用,不必为简单场景付出复杂场景的开销。

这里面有这样一个巧妙的设计,模型自动切换。当用户消息包含图片时,系统自动从文本模型切到视觉模型。
Java
if (prompt.getUserMessages().stream()
.anyMatch(m -> !CollectionUtils.isEmpty(m.getMedia()))) {
model = ModelConfig.ModelType.VISION;
}
业务 Agent 完全不用关心“用户发了图片该调哪个模型”,调用器在底层自动判断。
这种把基础设施决策下沉到公共层的做法,在多 Agent 系统里能显著降低每个 Agent 的实现复杂度。
05、历史消息怎么管理?
多轮对话场景下,Prompt 的消息列表会不断增长,最终超出大模型的上下文窗口。
求职派用智能窗口管理器解决了这个问题,分三步。

第一步,过滤无用消息。正则匹配纯确认类回复,“好的”“嗯”“OK”“收到”这些不携带实际信息的消息直接过滤。
Java
Pattern.compile(
"^(好的|收到|嗯|哦|好|知道了|明白了|ok|OK|yes)$",
Pattern.CASE_INSENSITIVE);
只过滤用户消息,模型回复即使很短也保留,因为短回复可能是关键确认。
第二步,token 估算与裁剪。中文字符按每字 2 个 token 计算,英文单词按 1.3 个 token 计算,加上每条消息 10 个 token 的结构开销。
超出预算时从最旧的消息开始删除,但保留最近若干条不动。
第三步,总条数兜底。即使 token 没超,消息条数超过上限也会裁剪。
Spring AI 自带的 MessageWindowChatMemory 只支持按条数裁剪,不区分消息的信息密度。
一条“嗯”和一条 500 字的技术讨论在它眼里是等权的。
求职派在这个基础上做了两个改进,基于 token 的裁剪让预算控制更精确,无用消息过滤把有限的窗口留给真正有价值的内容。尤其在中文场景下,一个汉字约等于 2 个 token,如果不做区分估算,纯按英文假设计算会严重低估实际 token 消耗。
当对话更长时,求职派还有一层会话摘要机制作为补充。
超过一定轮数的历史消息会被 LLM 压缩成一段摘要,以一条新的消息注入回上下文,这样既不丢失早期对话的核心信息,又把 token 占用压到原来的十分之一左右。
窗口管理和会话摘要配合使用,保证了求职派在长对话场景下的稳定表现。
从提示词构建到上下文管理,整条线环环相扣——Prompt 承载消息和参数,Message 的角色决定信息的定位,模板让提示词可复用可维护,调用器在底层统一处理模型选择和身份注入,窗口管理器则确保历史消息不会撑爆上下文。
提示词是大模型交互的起点,也是最容易被低估的环节。
【把提示词当作工程问题来对待,大模型才会真正按我们想要的方式工作。】
06、如何把求职派写到简历上?
项目名称:求职派(JobClaw)多 Agent 求职系统
项目简介:基于 Spring AI 2.0 的多 Agent 求职平台,支持微信/钉钉/飞书多渠道接入,通过意图识别、Agent 路由、用户画像和上下文记忆,为用户提供身份采集、岗位抓取、岗位推荐和求职问答服务。
技术栈:Java 21、Spring Boot 4.0、Spring AI 2.0、LangGraph4J、Reactor、YAML、H2/MySQL
核心职责:
-
基于 Spring AI 的 Prompt 和 ChatClient 抽象,封装 LLM 调用器(轻量/业务/用户偏好),统一处理模型选择、流式响应、会话记忆持久化和工具传参。
-
将 9 个提示词模板外置为 Markdown 资源文件,覆盖身份提取、灵魂画像、岗位抽取、会话摘要等场景,提示词与 Java 代码解耦,支持多 Agent 独立迭代,产品侧改模板不需要动业务代码。
-
实现智能上下文窗口管理,通过低效消息过滤、中英文 token 估算将多轮对话历史控制在预算内;支持超长对话触发异步摘要压缩,每 10 轮刷新一次,旧消息压缩为 100 字以内的摘要并归档。
-
设计用户身份注入机制,按系统规则、性格画像、基础信息、求职偏好四层维护用户档案,自动从对话中增量提取并拼接到系统提示词,同一个推荐 Agent 面对不同用户给出差异化推荐结果。
-
基于自定义 ReAct 替换 Spring AI 内置的自动工具执行,注册文件系统、Shell、MCP、网页抓取等十余种工具,并在推理-行动中插入中间件 Hook 钩子,支持 Function Calling 的日志记录和行为审计。