前言

大家好,我是直奔標杆!欢迎来到《Spring AI 零基础到实战》专栏的第四节,感谢各位小伙伴的持续关注与支持!

在上一节《Java开发者AI转型第三课!给大模型立“人设”!Spring AI 三大核心角色揭秘与高阶调参指南》中,我们通过System参数,成功给大模型赋予了各种贴合业务的灵魂与人设,相信大家已经感受到了Spring AI的便捷。但在真实的Java企业级开发中,用户的输入千变万化,我们总不能每次都用String的+号,去死磕长篇大论的提示词吧?

相信做过相关开发的小伙伴都有共鸣,传统开发中处理提示词,总有三大痛点:

  • 字符串拼接:代码又臭又长,可读性差,还极易漏掉空格或标点,排查起来费时费力;

  • String.format():满屏的%s,一旦变量变多,传参顺序极易出错,维护成本直接翻倍;

  • 强耦合:几百字的业务提示词和Java业务逻辑混写在一起,前端改个文案、调整个话术,都需要后端重新打包发版,效率极低!

为了解决这个困扰无数Java开发者的史诗级痛点,Spring AI特意引入了杀手锏——PromptTemplate (提示词模板)。它的核心思想,和我们平时常用的Freemarker、MyBatis动态SQL一模一样,就是要实现“静态文本”与“动态变量”的彻底解耦,让代码更简洁、维护更高效!

今天,咱们就一起彻底打通大模型输入端的任督二脉,不仅学会优雅处理提示词,还会教大家如何让AI“长出眼睛”,轻松看懂图片,解锁多模态交互新能力!

本节学习目标(建议收藏)

  • 底层解构:深入理解Prompt与Message的核心架构,看透Spring AI的参数封装哲学,知其然更知其所以然;

  • 模板魔法:熟练掌握PromptTemplate的使用,通过Java Map优雅完成占位符替换,告别硬编码;

  • 资源解耦:将超长提示词外置为.st文件,实现业务代码与AI文案的彻底分离,提升开发与维护效率;

  • 多模态识图:打破纯文本交互边界,使用Media接口实现图片上传,让AI完成视觉分析,解锁更多应用场景。

一、核心原理:Prompt渲染引擎

在动手敲代码之前,咱们先通过一个直观的逻辑,感受一下PromptTemplate的工作流程:一段包含占位符的静态文本模板,如何与Java的Map变量结合,最终生成大模型能听懂的终极指令?

其实原理很简单,就像我们平时用模板生成报表、邮件一样,把固定不变的文本抽成模板,动态变化的内容用占位符替代,最后通过变量注入,生成最终的内容——这就是Spring AI提示词渲染的核心逻辑。

Prompt到底是个啥?(源码视角解读)

在前面两节内容中,我们一直用ChatClient.user("...")进行无脑调用,快速实现与大模型的交互。今天咱们扒开Spring AI的源码,看看背后的核心逻辑:大模型通信的最基本单元,是一个叫做Prompt的对象。

// 源码路径:org.springframework.ai.chat.client.DefaultChatClientUtils
static ChatClientRequest toChatClientRequest(DefaultChatClient.DefaultChatClientRequestSpec inputRequest) {
        // ..... 
        return ChatClientRequest.builder().prompt(Prompt.builder().messages(processedMessages).chatOptions(processedChatOptions).build()).context(new ConcurrentHashMap(inputRequest.getAdvisorParams())).build();
    }

简单来说,Prompt就像是一个“指令包裹”,里面装了两样最核心的东西,缺一不可:

  1. List<Message>:消息列表,包含我们上一节讲过的SystemMessage(大模型人设)、UserMessage(用户提问)、AssistantMessage(大模型回复)等;

  2. ChatOptions:模型请求参数,比如控制生成随机性的Temperature、控制回复长度的MaxTokens等。

这里给大家划个重点(新手必看):PromptTemplate的作用,就是帮我们优雅、高效地动态生成这个Prompt中的Message对象,避免手动拼接带来的各种问题。

二、PromptTemplate实战演练(附完整代码,可直接复制使用)

理论讲再多,不如动手敲一遍。下面咱们结合3个实际开发场景,手把手教大家使用PromptTemplate,所有代码均经过实测,小伙伴们可以直接复制到项目中调试。

场景一:基础模板注入(两种常用写法)

在日常开发中,我们通常直接利用ChatClient提供的Fluent API,无缝集成模板渲染。默认情况下,PromptTemplate的占位符使用{变量名},下面给大家展示两种常用写法,按需选择即可。

/**
 * 写法1:常规方法,指定模板并通过param绑定变量(推荐日常开发使用)
 * @param subject 学科(动态变量)
 * @param message 用户提问
 * @return AI生成的回复
 */
@GetMapping("/api/teacher")
public String teacher(@RequestParam String subject, @RequestParam String message) {
     // 1. 定义包含{占位符}的纯文本模板(静态文本)
    String systemTemplate = "现在你是一名{subject}老师。";

    return this.chatClient.prompt()
            // 2. 注入System模板,并通过.param()绑定Map变量
            .system(sp -> sp.text(systemTemplate)
                    .param("subject", subject))
            // 3. User消息同理,也可以使用模板(此处直接传入用户输入,简化演示)
            .user(message)
            .call()
            .content();
}
/**
 * 写法2:直接构建Prompt对象,更灵活(适合复杂场景)
 * @param subject 学科(动态变量)
 * @param message 用户提问
 * @return AI生成的回复
 */
@GetMapping("/api/teacher2")
public String teacher2(@RequestParam String subject, @RequestParam String message) {
    // 1、构建用户消息(UserMessage)
    UserMessage userMessage = UserMessage.builder().text(message).build();
    // 2、使用模板创建系统消息(SystemMessage)
    String systemTemplate = "现在你是一名{subject}老师。";
    SystemPromptTemplate systemPromptTemplate = SystemPromptTemplate.builder()
            .template(systemTemplate)
            .variables(Map.of("subject", subject)) // 绑定变量
            .build();
    Message systemMessage = systemPromptTemplate.createMessage();

    // 3、构建消息列表,封装成Prompt对象
    Prompt prompt = Prompt.builder()
            .messages(Arrays.asList(userMessage, systemMessage))
            .build();

    // 调用大模型并返回结果
    return chatClient
            .prompt(prompt)
            .call()
            .content();
}

🔧 测试请求(直接复制到浏览器即可调用):

http://localhost:8080/api/teacher?subject=数学&message=你是谁

http://localhost:8080/api/teacher2?subject=数学&message=你是谁

✅ 预期AI回复(供参考):

我是你的数学老师,专门帮你解决数学问题、讲解概念、辅导作业,或者一起探讨有趣的数学话题。如果你有题目不会做、公式不理解,或者想挑战一些数学难题,随时告诉我,我们一起搞定! 😊

场景二:自定义占位符符号 <>(避坑必备)

小伙伴们注意了,实际开发中,我们的提示词里可能本身就包含大量的{}(比如要求AI输出JSON格式、代码块等),这时候如果占位符还是{},Spring AI的解析器就会混淆,导致模板渲染失败。

别慌!Spring AI支持自定义占位符分隔符,下面以<变量名>为例,给大家演示如何自定义:

/**
 * 自定义占位符(解决占位符与提示词中{}冲突的问题)
 * @param subject 学科
 * @return 渲染后的消息文本
 */
@GetMapping("/api/placeholder")
public String writePoem(@RequestParam String subject) {
    PromptTemplate promptTemplate = PromptTemplate.builder()
            // 自定义占位符:起始符<,结束符>
            .renderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
            .template("""
                    你是一个<subject>老师
                    """)
            .build();
    // 绑定变量并生成消息
    Message message = promptTemplate.createMessage(Map.of("subject", "美术"));
    return message.getText();
}

场景三:将提示词外置为.st文件(企业级开发推荐)

当提示词达到几百字甚至上千字时(比如构建RAG知识库、复杂Agent场景),把提示词硬编码在Java代码里,会导致代码臃肿、可读性差,还不利于维护。

Spring AI原生支持读取.st(StringTemplate)资源文件,我们可以将超长提示词外置,实现业务代码与AI文案的彻底分离,下面一步步教大家操作:

1. 新建.st文件:在src/main/resources目录下,新建prompts文件夹,然后在该文件夹下新建expert.st文件,写入以下内容(可根据自己的业务修改):

你是一个专业的 {role}。
你的核心任务是:{task}。

绝对遵守以下约束:
1. 不要捏造事实。
2. 必须以 {format} 的格式输出。

2. Java代码中通过@Value注入并使用(优雅又简洁):

// 注入.st文件资源
@Value("classpath:prompts/expert.st")
private Resource expertPromptResource;

/**
 * 读取外置.st文件,实现提示词与代码解耦
 * @return AI生成的分析结果
 */
@GetMapping("/api/expert")
public String useResourceTemplate() {
    return this.chatClient.prompt()
            // 直接传入Resource对象,自动解析.st模板
            .system(sp -> sp.text(expertPromptResource)
                    .param("role", "Java 性能调优专家")
                    .param("task", "分析长 GC 暂停的原因")
                    .param("format", "Markdown 列表"))
            .user("我的系统经常出现 5 秒以上的 STW,请问怎么排查?")
            .call()
            .content();
}

💡 小贴士:这样一来,后续需要修改提示词文案,只需要修改.st文件,无需改动Java代码,也不用重新发版,架构瞬间清爽,维护效率直接拉满!

三、多模态识图 (Vision) 实战(解锁AI视觉能力)

现在的大模型早已不局限于处理文本,像GPT-4o、通义千问-VL、本地Ollama部署的Qwen3.5等,都具备多模态(Multi-modal)能力,能够识别图片、音频等。

在Spring AI中,让AI识别图片简直易如反掌,核心就在于Media对象——我们只需要将图片与UserMessage组合在一起,发送给大模型,就能实现图片识别。

下面结合实际案例,教大家实现本地图片识别(附完整代码):

前提:在src/main/resources/目录下,放入一张名为tupian.jpg的图片(可替换为自己的图片,注意修改文件名)。

/**
 * 多模态识图实战:使用GPT-4o(也可使用本地Ollama部署的Qwen3.5)
 * @return AI对图片的分析结果
 */
@GetMapping("/api/image-vision")
public String testImageVision() {
    // 1. 从classpath读取本地图片资源
    Resource imageResource = new ClassPathResource("tupian.jpg");

    // 2. 构建多模态UserMessage(文本+图片)
    UserMessage userMessage = UserMessage.builder()
            .text("查看这张图的内容,详细描述图片中的元素") // 提示词,引导AI分析图片
            .media(Media.builder()
                    .mimeType(MimeTypeUtils.IMAGE_JPEG) // 指定媒体类型(JPG格式)
                    .data(imageResource)               // 塞入图片数据
                    .build())
            .build();

    // 3. 封装进Prompt,发送给大模型
    Prompt prompt = new Prompt(userMessage);

    // 调用大模型,返回图片分析结果
    return chatClient
            .prompt(prompt)
            .call()
            .content();
}

⚠️ 重要注意事项:请确保你配置的底层大模型支持视觉能力,比如GPT-4o、通义千问-VL,或者本地Ollama部署的开源多模态模型(如Qwen3.5-VL),否则会识别失败。

拓展思考:有了这个能力,我们可以轻松开发出很多实用应用,比如“发票识别报销系统”“花草识别小程序”“图片内容审核工具”等,大大拓展Java应用的边界!

四、必备技巧:写好System Prompt的CRISPE原则

很多小伙伴会陷入一个误区:觉得PromptTemplate好用,就忽略了提示词本身的质量。要知道,Prompt模板再好用,里面填的内容如果是“垃圾”,AI产出的也会是“垃圾”(这就是行业内常说的Garbage In, Garbage Out)。

在企业级开发中,建议大家遵循CRISPE原则(核心4要素),来编写你的.st模板或提示词,确保AI输出符合预期、准确可靠:

  • Role (角色设定):明确AI的身份,比如“你是一个严谨的法务顾问”“你是一个资深的Java架构师”,让AI知道自己该以什么身份回应;

  • Task (任务目标):明确AI要完成的任务,比如“审查以下合同文本中的霸王条款”“分析这段Java代码的性能瓶颈”,避免AI偏离方向;

  • Constraints (约束条件):这是防AI幻觉的核心!比如“如果遇到不确定的内容,必须回答‘无法确定,请补充信息’,严禁自行捏造”“只能使用提供的知识库内容进行回复”;

  • Format (输出格式):明确AI的输出样式,比如“请输出JSON格式,包含{条款内容, 风险等级, 修改建议}”“请以Markdown列表形式输出,分点清晰”。

五、本节总结(重点回顾)

本节课,我们彻底告别了丑陋又繁琐的字符串拼接,核心收获有3点,大家可以对照自查,看看是否掌握:

  1. 掌握PromptTemplate的使用,通过Java Map优雅完成占位符替换,解决硬编码痛点;

  2. 学会将超长提示词外置为.st文件,实现业务代码与AI文案的彻底解耦,提升开发与维护效率;

  3. 解锁多模态识图能力,通过Media接口实现图片上传与AI视觉分析,拓展Java应用场景。

这些知识点,都为我们后续构建企业级复杂的RAG(检索增强生成)知识库,打下了极其坚实的基础,大家一定要多动手调试,加深理解。

但是,新的问题又来了:

在当前的演示中,AI无论多聪明,返回给我们的始终是一大长串String字符串。而在前后端分离的现代化开发中,前端需要的是规规矩矩的JSON对象(比如{"name": "张三", "age": 18}),难道我们要用正则表达式,去从AI的回答里苦哈哈地抠取数据吗?

答案当然是:不!千万不要用正则去解析AI的回答,既麻烦又容易出错!

下节预告(值得期待)

在第5节《Java开发者AI转型第五课!让AI懂规矩!Spring AI 结构化输出 (DTO) 映射与 Flux 流式打字机极速响应》中,你将见证Spring AI最惊艳的功能之一!

我们将一起学习:如何让AI直接返回一个完美的Java DTO / List对象,彻底打通AI与传统业务系统(如存入数据库)的最后一公里;以及如何通过Flux流式输出,缓解用户等待焦虑,提升交互体验。

精彩继续,咱们下节见!

专栏往期内容(循序渐进,建议按顺序学习)

💬 互动交流:大家在使用PromptTemplate或多模态识图时,遇到了哪些问题?欢迎在评论区留言讨论,一起学习、一起进步!我会及时回复每一条留言~

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐