ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringAI项目实战:基于ChatClient与提示词工程实现内容审核系统

SpringAI项目实战:基于ChatClient与提示词工程实现内容审核系统 第一次接触 SpringAI 项目的时候我一度被文档里的概念搞得有点懵ChatModel、ChatClient、Message、Prompt、Token 上下文、系统提示词……这些名词跟之前熟悉的 JPA、MyBatis 完全是两套画风。等真正把一个请求跑通、把一次内容审核逻辑写完才意识到它对 Java 后端开发者的价值有多大——它本质上是一套把“大模型 API 调用”变成“Spring 风格开发体验”的官方套件。这篇内容主要面向两类人一类是想在 Java/Spring Boot 项目里接入大模型能力的后端工程师另一类是准备做 AI 应用开发、但还没想清楚从哪入手的初学者。我会从 SpringAI 项目的基础概念讲起带你把环境搭起来、把第一个对话接口跑通再结合一个“内容智能审核”的真实场景把系统提示词配置、结构化输出这些关键知识点全部过一遍最后把我踩过的坑和排查经验整理成清单。1. SpringAI 项目到底是什么以及它帮我解决了什么1.1 先捋清楚SpringAI 在项目中处于哪一层很多新手会把“大模型”“AI 应用开发”“SpringAI”这几个概念混在一起。先拆开看。大模型本身是一个提供文本补全、对话、推理能力的外部服务比如 DeepSeek、通义千问等它们通过 HTTP API 对外提供服务AI 应用开发则是你把这些模型的“文本理解与生成”能力嵌入到你自己的业务流程中比如做智能客服、内容审核、文档摘要、代码生成而 SpringAI 就是帮助你更高效地完成这件事的 Java 框架。SpringAI 是 Spring 官方推出的 AI 应用开发框架它的地位相当于 Java 生态中的“AI 客户端”。我之前没接触它时要在大模型 API 调用中做这些事情手写 HTTP 请求、拼接 JSON 消息体、解析返回结果、处理流式输出、管理多轮对话历史。这套流程每一步都不难但每一步都很琐碎。而 SpringAI 把这些工作抽象成一套 Spring 风格的 API让开发者像写普通业务代码一样调用大模型能力。用一个生活化的类比来说大模型 API 就像一家只提供“标准菜品”的餐厅你每次都要自己写菜单、递单子、等菜、验菜SpringAI 则是你熟悉的点菜助手你只需要告诉它你想吃什么它会帮你完成下单和上菜的动作并且把菜摆得整整齐齐。它不负责做饭模型能力是外部服务提供的但负责把整个用餐体验理顺。SpringAI 项目适合跑在 Spring Boot 3.x 的 Java 应用里面是典型的三层定位最底层是大模型 API外部服务中间层是 SpringAI 的模型抽象与自动配置最上层是你自己的业务代码。理解了这一层关系就不会再问“SpringAI 是不是要替代大模型”这种问题了——它只是桥梁。1.2 SpringAI 的核心抽象ChatClient 与 ChatModelSpringAI 中最重要的两个对象一个是 ChatModel另一个是 ChatClient。新手一开始容易把它们搞混我用自己的理解解释一下。ChatModel 是对“底层大模型服务”的抽象负责跟具体的大模型供应商打交道。比如你用 OpenAI 兼容接口接入了 DeepSeek那么底层就会创建一个 OpenAiChatModel 实例它知道怎么把消息组装成 HTTP 请求、怎么解析响应。ChatClient 则是对外提供的一套更高级、面向业务编码的流式 API它内部会持有 ChatModel并帮你处理提示词组装、消息历史、结构化输出、工具调用等细节。简单地说ChatModel 是“能打电话的人”ChatClient 是“能帮你把话说清楚、把事办妥的助理”。在实际的 Spring Boot 项目中你并不需要手动创建 ChatClient。SpringBoot 的自动配置会在容器里放好 ChatClient.Builder你只要把它注入进自己的类里然后调用 .build() 生成一个 ChatClient 即可。我建议你所有业务代码都操作 ChatClient而不要直接操作 ChatModel因为 ChatClient 提供的方法更贴近业务语义比如 .prompt()、.system()、.user()、.call()读起来就像在描述一段对话。另外SpringAI 里还有一个非常重要的概念叫 Message。Message 有大模型对话中常见的三种角色SystemMessage、UserMessage、AssistantMessage。SystemMessage 是系统提示词用于设定模型的角色和行为边界UserMessage 是用户输入AssistantMessage 是模型回复。多轮对话时这些消息会按顺序组成一个 Prompt 发送给模型。这个设计跟大模型 API 的 messages 数组是一一对应的逻辑很清晰。// 核心结构示意简化版 ChatClient chatClient ChatClient.builder(chatModel).build(); String result chatClient.prompt() .system(你是 Java 开发助手回答尽量简洁。) .user(什么是 SpringAI) .call() .content();2. 环境准备从零搭起一个可运行的 SpringAI 项目2.1 依赖引入的版本选型与踩坑记录SpringAI 项目对环境有硬性要求JDK 17 及以上Spring Boot 3.2 以上建议用 3.4 或 3.5 系列。如果你在创建项目时拿不准版本最稳妥的做法是直接到 Spring Initializr 网站把 Spring AI 依赖加进去后生成项目它会自动帮你匹配好兼容的 Spring Boot 版本避免出现“依赖引入了启动就报 NoSuchMethodError”这类经典问题。我自己踩过的一个坑手工引入了 SpringAI 1.0.0 的依赖但项目里是 Spring Boot 3.1结果启动时直接因为自动配置类引用了不存在的类失败。后来统一用 Initializr 重新生成问题立刻消失。如果你非要手工管理那么记得在 pom.xml 里加上 Spring AI BOM 统一管理版本同时注意 SpringAI 的 GA 版本发布节奏较快API 变动不小网上很多教程是基于 0.8.x 或 1.0.0-M 系列写的方法名可能对不上看的时候要留意版本号。在 pom.xml 中引入依赖的核心写法如下。注意如果你使用 1.0.0-M 之前的里程碑版本还需要额外在 中配置 Spring 的里程碑仓库使用正式版则无需配置。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.4.5/version relativePath/ /parent dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version1.0.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- OpenAI 兼容模块可接入 DeepSeek 等模型 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId /dependency /dependencies2.2 application.yml 中需要关注的核心配置依赖引入之后接下来就是模型接入配置。我建议新手先不要搞一堆自定义封装直接用 OpenAI 兼容接口配置把 DeepSeek 或者其它国产模型接上先跑通最小流程再逐步加东西。这里的关键配置项有三个API Key、Base URL、模型名称。API Key 是你调用模型服务的凭证一般用环境变量读取不要写死在代码里。Base URL 是模型服务的接口地址如果用的是 DeepSeek 官方 API就配置为 https://api.deepseek.com因为它的接口风格与 OpenAI 兼容所以能直接复用 SpringAI 的 OpenAI 模块。模型名称则取决于你想用什么模型比如 deepseek-chat 是 DeepSeek 的对话模型deepseek-reasoner 是其推理模型。我自己的习惯是这样配置实测下来很稳spring: application: name: springai-demo ai: openai: api-key: ${DEEPSEEK_API_KEY:sk-please-replace-me} base-url: https://api.deepseek.com chat: options: model: deepseek-chat temperature: 0.7 max-tokens: 2048这里有一个细节值得注意application.yml 中配置的 temperature 和 max-tokens 是全局默认值。如果你想给某个特殊接口单独设置不同参数后面的章节会讲到如何在代码里覆盖它。配置完成后Spring Boot 会自动配置 OpenAiChatModel 和 ChatClient.Builder 这两个 Bean不需要你手动 new。2.3 第一个可跑通的对话接口跑通项目最简单的方式是直接写一个 Controller注入 ChatClient.Builder然后在 GetMapping 方法里调用对话能力。这个 Demo 虽然短但它能帮助你验证环境是否正常、依赖是否配齐、网络链路是否通。RestController RequestMapping(/ai) public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient builder.build(); } GetMapping(/chat) public String chat(RequestParam(defaultValue 你好请用一句话介绍你自己) String message) { return chatClient.prompt() .user(message) .call() .content(); } }启动项目后访问 http://localhost:8080/ai/chat如果一切正常几秒内你会看到模型返回的文本内容。第一次看到返回结果时那种“大模型接入到自己项目里”的感觉确实挺爽的但请先别急着搞功能我建议你顺手把日志级别打开真正理解一次请求发生了什么。在 application.yml 中加上这段配置然后再跑一次观察控制台日志logging: level: org.springframework.ai.chat.client: DEBUG org.springframework.ai.model: DEBUG你会发现 SpringAI 把消息组装成了 JSON 请求体然后通过 RestClient 发送给模型服务最后解析响应。看清楚这个过程后后面遇到问题时的定位思路就清晰了——是请求组装问题、网络问题还是响应解析问题直接从日志里就能看出来。3. 核心 API 实操调用方式、系统提示词与结构化输出3.1 ChatClient 的三种打开方式我建议你主用哪种ChatClient 的调用方式会直接影响代码的可读性和扩展性。我总结了三种最常见的方式你可以根据场景自行选择。第一种是最简单直接的chatClient.call(String)适合快速验证、写测试用例。这个方法会把你传入的字符串当作 user 消息发给模型然后返回字符串结果。优点是简单到极致缺点是不太灵活——你没法指定系统提示词也没法调整参数。它只适合作为“Hello World”级别的最小验证。第二种是我日常开发中最常用的chatClient.prompt()链式调用。它返回一个 PromptRequestSpec 对象你可以通过 .system()、.user()、.options() 等方法来组装一次完整的请求最后调用 .call() 或 .stream() 来执行。这种方式的语义非常接近自然对话读代码的人一眼就能看懂“当前给的系统提示词是什么、用户输入是什么”。我强烈建议新人在正式业务代码里用这种写法不要为了方便而直接跳回第一种方式。第三种是ChatModel.call(Prompt)直接调用。这是最灵活也最繁琐的方式你需要手动构造 Prompt、Message 对象适合做深度定制或者封装自己的工具库。普通业务开发完全没必要直接用它。举个例子你就懂了ChatModel 就像手动挡汽车什么都要自己控制ChatClient 里的 prompt() 是自动挡日常开省心得多。我建议你主用第二种把第一种留给测试第三种留给“你需要完全掌控每个细节”的少数场景。// 方式一极简调用适合测试 String answer chatClient.call(11等于几?); // 方式二链式调用推荐日常主用 String answer chatClient.prompt() .system(你是一个小学数学老师回答要简单耐心。) .user(11等于几?) .call() .content(); // 方式三底层调用适合深度定制 Prompt prompt new Prompt(new UserMessage(11等于几?)); String answer chatModel.call(prompt).getResult().getOutput().getText();这里有个性能细节ChatClient 是线程安全的你在 Controller 里作为单例使用完全没问题。不需要每次请求都调用 builder.build() 去创建新实例那样反而浪费资源。我自己曾见过同事在每个方法里 new 一个 ChatClient虽然功能正常但明显增加了无意义的对象创建开销。3.2 系统提示词的配置姿势代码内联、模板外部化与动态拼装系统提示词是 SpringAI 项目里非常重要的一环它决定了模型以什么角色、按什么规则来回答。如果配置不当再好的模型也会“跑偏”。我在实际项目中配置系统提示词一般会分三种姿势。最简单的一种是直接在代码里传入字符串适合写死角色设定。比如chatClient.prompt().system(你是一个严谨的翻译引擎只输出译文不解释其他内容)。优点是直观缺点是硬编码提示词一长就显得代码很臃肿。我建议这种姿势只用于演示或极简场景。第二种是模板化配置。SpringAI 提供了 PromptTemplate你可以把系统提示词中的核心内容做成占位符再通过参数动态填充。这种方式非常适合审核、客服、摘要这类业务逻辑固定的场景。举个例子如果你做内容审核审核规则基本不变只有被审核内容在变化。你可以把规则写成模板把“待审核内容”作为变量拼进去。String systemTemplate 你是一名内容审核员请根据以下规则判断用户输入是否违规 规则一不得包含辱骂攻击性言论。 规则二不得包含垃圾广告或骚扰信息。 规则三不得包含违法违规内容。 用户输入内容为 {input} 如果违规请回复“违规”并说明理由如果不违规请回复“通过”。 ; String userContent 你真是个蠢货我要投诉你; PromptTemplate template new PromptTemplate(systemTemplate, Map.of(input, userContent)); Message message template.createMessage(); String result chatClient.prompt() .system(你是内容审核机器人必须客观判断不带有任何情绪。) .user(message.getContent()) .call() .content();第三种是把提示词外部化放到 resources/prompts/ 目录下面使用 Spring 的 Resource 机制来加载。这样做的好处是提示词跟代码分离运营同学可以直接调整话术和规则不用动代码重新发布。我见过很多团队最终都会走到这一步因为 AI 应用的提示词迭代频率太高了动不动改一版放在代码里实在难受。Value(classpath:prompts/review-system.txt) private Resource systemPromptResource; public String review(String content) { String systemPrompt systemPromptResource.getContentAsString(StandardCharsets.UTF_8); // 后续继续使用 systemPrompt 组装请求 }最后提醒一点系统提示词不是越长越好。新手容易把能想到的规则全写进去结果反而干扰模型判断。我实测下来提示词应尽量明确、结构清晰、避免自相矛盾。给模型限定“什么不能做”不如更直接地告诉它“应该输出什么格式”更有效。3.3 结构化输出让模型返回你想要的 Java 类型搞 AI 应用开发最难受的一件事就是模型返回的是纯文本你还要用字符串解析去提取关键信息。SpringAI 的结构化输出功能帮了大忙——它可以把模型的返回结果直接映射成 Java 对象比如 Record 或 POJO。结构化输出底层做的事情是在请求里要求模型“只输出 JSON”然后把模型返回的 JSON 字符串通过 Jackson 反序列化成目标类型。这个机制听起来简单但在 SpringAI 中封装得很好你只需要一行代码.call().entity(YourClass.class)。它的使用门槛极低但有一个前提要求目标类型必须能被 Jackson 正确反序列化。我强烈建议使用 Java Record 来定义返回结构因为它是规范的数据载体且天然友好于 JSON 反序列化。下面是一个典型的结构化输出示例public record ReviewResult( boolean pass, String reason, String suggestions ) {}ReviewResult result chatClient.prompt() .system( 你是一名社区内容审核员请你对用户内容进行审核。 必须按以下 JSON 格式返回结果 {pass: true或false, reason: 审核理由, suggestions: 修改建议或无} ) .user(content) .call() .entity(ReviewResult.class);这里有几个关键点。第一系统提示词里一定要明确要求模型返回 JSON 格式并给出对应的字段名称和含义。第二字段名要和 Record 的属性名保持一致否则反序列化会失败或得到 null。第三如果模型输出的 JSON 格式偶尔不合法SpringAI 会抛异常所以在生产环境建议加 try-catch 并给一个默认的兜底结果。我在多次实践中发现结构化输出的稳定性受温度参数影响很大。温度调得太高模型容易“自由发挥”输出格式不稳定温度调低到 0.2 以下它基本会乖乖按格式输出。所以在需要严格结构化输出的场景我一般默认 temperature 设为 0.1 或 0.2宁可让它“呆板”一点也不要影响程序正确性。4. 实战案例用 SpringAI 做一个内容智能审核模块4.1 场景拆解为什么智能审核用大模型而不是正则内容审核是个很现实的需求。论坛、电商评论、聊天室、用户生成内容UGC场景中你都需要过滤掉吵架、广告、垃圾信息甚至更严重的违规内容。传统做法是用关键词黑名单正则表达式简单粗暴。但它的短板太明显了语义变化多样、绕词表达很难覆盖、误杀率也高。比如“这是不是广告”与“这广告真好看”意思完全不同正则很难区分。用大模型做智能审核的优势在于它能理解语义和上下文。你给它一套审核规则它就能像一名人工审核员一样做出判断。SpringAI 适合做这件事的另一个原因是它的结构化输出能力让“审核结果”可以直接落成一个 Java 对象你不需要自己去解析一大段文本这与后端业务集成非常顺畅。在设计这个智能审核模块时我建议先明确它的输入和输出。输入是用户提交的一段文本内容输出应包含三个信息是否通过、审核理由、修改建议。这样设计的好处是前端拿到结果后可以直接给用户展示反馈后台也可以把审核结果存库留作证据。一个完整的智能审核模块通常由三部分组成审核规则的定义与提示词模板、核心调用逻辑SpringAI 实现、以及兜底容错机制。下面我按这三个部分拆开来讲。4.2 提示词模板设计与 Java 代码实现提示词模板是智能审核模块的灵魂。我在实际项目中总结出一个经验好的审核提示词必须包含三块内容——角色定位、明确规则、输出约束。角色定位告诉模型“你是谁”明确规则告诉模型“按什么标准判断”输出约束告诉模型“最后必须给我什么格式的结果”。三块缺一不可尤其输出约束如果不写模型会给你输出各种各样的解释文字结构化输出基本就废了。这是一份可以直接套用的系统提示词模板我把它放在 resources/prompts/review-system.txt 里方便随时调整。你是一名专业的社区内容审核员。请对用户提交的内容进行安全审核。 审核标准 1. 是否存在辱骂、人身攻击或恶意骚扰言论。 2. 是否存在垃圾广告、引流、重复刷屏等骚扰类内容。 3. 是否包含违法违规内容或违反公序良俗的表述。 判定要求 - 只要命中以上任意一条pass 即应为 false。 - 如果内容正常pass 为 truesuggestions 返回“无”。 - 判断要客观可根据整体语气和上下文判断不必过度敏感。 输出格式必须严格 JSON {pass: true 或 false, reason: 不超过50字的审核理由, suggestions: 针对不通过内容的修改建议若通过则填无}审核拒绝示例供参考 用户输入你们这个平台就是垃圾管理员全是脑残。 期望输出{pass: false, reason: 存在辱骂攻击性言论, suggestions: 请修改为客观反馈例如平台体验有待改进。}注意我在提示词里加了“审核拒绝示例”这是提高模型稳定性的一个关键技巧。给模型一个 few-shot 示例比写十句规则都管用。示例不必太多一个就足够但一定要跟你的业务场景贴合。 核心调用逻辑代码如下 java Service public class AIContentReviewService { private final ChatClient chatClient; Value(classpath:prompts/review-system.txt) private Resource systemPromptResource; public AIContentReviewService(ChatClient.Builder builder) { this.chatClient builder.build(); } public ReviewResult review(String content) { try { String systemPrompt systemPromptResource.getContentAsString(StandardCharsets.UTF_8); return chatClient.prompt() .system(systemPrompt) .user(content) .call() .entity(ReviewResult.class); } catch (Exception e) { // 兜底逻辑审核接口不能因为模型异常直接挂掉 return new ReviewResult(false, 审核服务暂时不可用请稍后重试, 系统繁忙请稍后再试); } } }这段代码有两个我特别想强调的设计。第一systemPromptResource是在 Bean 初始化时加载的不会每次请求都重新读文件性能没问题。第二当捕获到异常时我选择返回一个“默认不通过”的兜底结果。社区内容审核宁可误伤也不能漏放这是安全底线上的取舍。虽然这个兜底会拦截一些正常内容但能避免把违规内容放出去。4.3 效果调优从“能跑”到“能用”的三个参数很多新手写完这个模块后发现“能跑”和“能用”之间还有很大距离。我在实际调优时主要盯着三个参数和两个技巧。第一个参数是 temperature。审核场景要求输出稳定所以我会把它调低。在配置里我通常设置为 0.2如果你在某个接口里需要单独覆盖可以用.options()方法chatClient.prompt() .system(systemPrompt) .user(content) .options(ChatOptions.builder().temperature(0.2).maxTokens(512).build()) .call() .entity(ReviewResult.class);第二个参数是 max-tokens。审核结果不需要生成很长的文本我实测 512 就完全够用了。这个参数直接影响成本和响应速度限制到 512 后响应时间明显更短费用也会更省。如果你不限制模型有可能会写很多“废话”白白浪费 token。第三个参数是模型选择。如果你接入了 deepseek-chat 和 deepseek-reasoner 两类模型审核场景我最推荐 deepseek-chat因为它是纯生成模型响应快、成本低deepseek-reasoner 适合复杂推理任务放在审核场景里既不划算也显得大材小用。技巧方面第一个是“二次验证”。对于高风险内容我建议用模型审核两次取“从严”的结果。比如第一次判断通过第二次判断不通过那就按不通过处理。这个技巧非常简单但效果巨大基本能消灭大部分漏网之鱼。第二个是记录模型返回的 reason 字段它会成为后续人工复核时的重要参考。别小看这一点实际运营中审核结果为什么被拦截、是机器判的还是人工判的都需要有迹可循。5. 新手常见问题与排查技巧实录5.1 启动失败、401、超时这类“最基础但最烦人”的问题新手阶段遇到的问题往往并不高深却很磨人。我梳理了几个出现频率最高的问题并附上我的排查思路。第一个是项目启动失败。现象是控制台直接报NoSuchBeanDefinitionException或ClassNotFoundException。这种有九成是 Spring Boot 与 SpringAI 版本不匹配导致的。我的排查顺序是先确认 JDK 版本是不是 17 以上再确认 Spring Boot 版本和 SpringAI 版本是否能对上最后看依赖有没有成功下载。最省心的办法就是去 Spring Initializr 重新生成一个项目而不是在旧项目里死磕版本。第二个是调用接口时返回 401 Unauthorized。这几乎都是 API Key 配置错误。排查时先确认环境变量是否已经设置再确认 key 是不是复制完整有没有多余的空格。我见过太多人把 key 直接写在 yml 里然后在本地能跑部署到服务器上就 401就是因为环境变量没同步过去。还有一点要注意不要把 key 提交到 Git 仓库里这个教训是用真金白银换来的。第三个是请求超时。模型服务本来响应就慢如果请求内容复杂、生成的文本长读超时时间不够就会报错。这时候你需要调整底层 HTTP 客户端的连接超时和读取超时。SpringAI 底层用了 RestClient你可以配置对应的 httpclient 超时参数。我一般在配置类里把读取超时调到 60 秒避免长文本生成时半路断开。Bean public ClientHttpRequestFactory clientHttpRequestFactory() { var factory new JdkClientHttpRequestFactory(); factory.setReadTimeout(Duration.ofSeconds(60)); factory.setConnectTimeout(Duration.ofSeconds(10)); return factory; }这里提醒一个容易忽略的点不要直接把超时设置得过大这会拖垮接口整体响应时间。如果你需要长时间生成宁可改造为流式输出也不要让一个同步接口等上好几分钟。5.2 并发场景下的性能与稳定性调优当你的 SpringAI 接口开始被真实用户调用时并发问题马上就会暴露。我先说结论ChatClient 本身是线程安全且无状态的单例使用即可不需要为每个请求创建新实例。但真正需要关注的是下游大模型服务的并发限制。大多数模型 API 都有 QPS 或 Token 每分钟限额超过之后会返回 429 限流错误。如果你的后端接口被恶意刷量或者业务量突然上升模型服务很容易被打爆。我在项目中一般加入一个信号量Semaphore来进行简单的并发控制限制同时进行中的模型请求数Service public class SemaphoreAIService { private final Semaphore semaphore new Semaphore(10); public String callWithLimit(String prompt) { boolean acquired semaphore.tryAcquire(); if (!acquired) { throw new RuntimeException(并发过大请稍后重试); } try { return chatClient.prompt().user(prompt).call().content(); } finally { semaphore.release(); } } }这是最快的限流方案。如果你需要更完善的熔断、降级、重试策略建议引入 Resilience4j。重试一定要谨慎大模型请求不是幂等的盲目重试可能造成重复扣费。我建议只在“连接超时、网络抖动”这类场景下重试不要对“业务返回异常”重试。并发场景下还有一个容易被忽略的点连接池大小。如果底层 HTTP 连接池太小即使你的业务线程足够多也会全部阻塞在等待连接上。具体怎么调整要看项目用的 HTTP 客户端但记住一个原则连接池上限至少要能覆盖你预期的最大并发数否则一切并发配置都是空谈。5.3 提示词工程中容易被忽略的三个原则最后聊几个我在调提示词时总结出来的原则这些经验不是从文档里抄来的全是实际项目里踩过坑之后的体会。第一个原则是“先说角色再讲规则最后给示例”。这个顺序看着简单但能显著提升模型的理解效果。你如果一上来就丢一堆规则模型可能抓不住重点先告诉它“你是谁、你承担什么职责”再给它规则它就更清楚该按什么框架去判断。给示例时优先给“反例”因为模型学习“什么不能做”比学习“什么能做”更高效。第二个原则是“给输出格式下明确指令”。回到前面提到的结构化输出问题你在提示词里写“请返回 JSON”模型可能会返回一个带 Markdown 代码块的 JSON你写“只返回 JSON不要解释”它才更老实。实际上 SpringAI 的 entity 方法对 JSON 字符串有一定解析能力但最稳妥的做法是提示词里就切断所有歧义。第三个原则是“提示词也是需要维护的代码”。AI 应用的提示词会随着业务规则调整而频繁变化我强烈建议把它纳入版本管理放在独立的配置文件或模板文件中而不要硬编码在 Java 代码里。你可以为提示词编写单元测试用固定的输入校验输出是否包含预期字段。这样当运营改了一版话术时你第一时间就能发现它是否影响了审核逻辑。最后再分享一个小技巧排查 ChatGPT 或大模型相关的异常时第一反应不要去看业务代码而是先把请求日志的完整链路打开。我看到无数新手在代码里 debug 半天结果发现问题是 API Key 多了一个空格或者超时时间太短。先确认“请求是否发出去、响应是否已回来”再去怀疑代码逻辑排查效率会高出一大截。我个人的习惯是每接到一个 SpringAI 项目第一件事就是把请求层日志打开跑通一个最小用例再开始写业务代码。你看完这篇文章后不妨也按这个顺序去实践一次很快你就能摸透 SpringAI 项目的脾气。
返回列表