前言 把 ChatClient 的用法过了一遍之后,可以单独看一下 Prompt,分析写什么样子的 Prompt 才能让模型更好的干活(虽然可能都被讲烂了)。
System Prompt 和 User Prompt 的职责 在项目中很多人一开始就把所用东西都塞入 user 消息中,但这不是最好的做法。
System Prompt 是给模型的“岗位说明书”,定义:
模型需要扮演什么角色 有哪些能力限制 输出格式 遇到某种情况需要如何处理 User Prompt 是每次用户发送的具体问题,内容是变化的。
好的做法是把稳定的、全局的约束 放在 System Prompt,把动态的、每次不同的内容 放在 User Prompt。
System Prompt 的写法:五要素 角色(Role) 告诉模型它是谁:你是一个资深的安全研究员,有10年的漏洞挖掘经验。
角色的设定会影响模型的输出风格、用词和视角。技术问题用技术专家,客服问题用客服助手。
人物(Task) 明确它要做什么:你的任务是帮助用户分析漏洞代码,找出代码中的逻辑漏洞或危险函数等
约束(Constraint) 告诉它不能够做什么,或者有什么限制:只回答Pwn方向相关的问题,其他问题一律回复“这个问题超出我的服务范围”
回答时不要做过度解释,直接给出结论和解决代码
输出格式(Format) 告诉它使用什么格式输出回答使用 Markdown 格式,关键词加粗 举例使用有序或无序列表
示例(Example) 给几个正确的例子,特别是当任务比较特殊时:
Spring AI 中写 System Prompt(System和defaultSystem) 直接写在 Builder 里(对该实例全局生效) 在 service 或 Controller 的构造方法里,用 defaultSystem 把 System Prompt 固定下来,后续这个 ChatClient 实例每次调用都会自动带上它。
com/berial/springai/service/JavaTechService.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package com.berial.springai.service;import org.springframework.ai.chat.client.ChatClient;import org.springframework.stereotype.Service;@Service public class JavaTechService { private final ChatClient chatClient; public JavaTechService (ChatClient.Builder builder) { this .chatClient = builder .defaultSystem(""" 你是一个专业的 Java 技术助手。 职责: - 回答 Java、Spring Boot、Spring AI 相关的技术问题 - 帮助用户 debug 代码 - 提供最佳实践建议 规则: - 代码示例使用 Java 17+ 语法 - 回答简洁,不要过度解释 - 不确定的内容要说明,不要编造 - 非技术问题礼貌拒绝 输出格式: - 使用 Markdown格式 - 代码使用代码块包裹 """ ).build(); } public String ask (String question) { return chatClient.prompt() .user(question) .call().content(); } }
Controller调用
com/berial/springai/controller/prompt/JavaTechController.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.berial.springai.controller.prompt;import com.berial.springai.service.JavaTechService;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("/java-tech") public class JavaTechController { private final JavaTechService javaTechService; public JavaTechController (JavaTechService javaTechService) { this .javaTechService = javaTechService; } @GetMapping("/ask") public String ask (@RequestParam String question) { return javaTechService.ask(question); } }
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 GET http://localhost:8080/java-tech/ask?question=%E5%B8%AE%E6%88%91%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E9%99%A4%E9%9B%B6 HTTP/1.1 200 Content-Type : text/plain;charset=UTF-8Content-Length : 883Date : Tue, 24 Mar 2026 09:06:13 GMT当然可以。下面是一个简单的 Java 方法,用于执行除法操作并处理除零异常: ```java public class DivisionExample { public static void main (String[] args ) { int numerator = 10 ; int denominator = 0 ; try { int result = divide(numerator, denominator); System.out .println("结果是: " + result); } catch (ArithmeticException e) { System.out .println("错误: 除数不能为零" ); } } public static int divide (int numerator, int denominator ) { if (denominator == 0 ) { throw new ArithmeticException("除数不能为零" ); } return numerator / denominator; } } ``` 这个示例中,`divide` 方法会检查除数是否为零,如果是零则抛出 `ArithmeticException` 异常。在 `main` 方法中捕获并处理这个异常。 Response code: 200 ; Time: 6964 ms (6 s 964 ms); Content length: 701 bytes (701 B)
这是上篇文章中 ChatClient 多实例写法的延伸,因为每个 Service 拿到的 ChatClient.Builder 是独立的,defaultSystem() 只影响当前实例,不影响其他地方注入的 ChatClient。
每次调用时动态设置(局部生效,覆盖默认) com/berial/springai/controller/prompt/CodeReviewController.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.berial.springai.controller.prompt;import org.springframework.ai.chat.client.ChatClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RestController public class CodeReviewController { private final ChatClient chatClient; public CodeReviewController (ChatClient.Builder builder) { this .chatClient = builder.build(); } @GetMapping("/review") public String CodeReview (@RequestParam String code) { return chatClient.prompt() .system(""" 你是一个资深的 Java 工程师,正在做 Code Review。 找出代码中的: 1. Bug(包括潜在的运行时错误) 2. 性能问题 3. 不符合 Java 最佳实践的写法 输出格式: 每个问题单独列出,格式为: 【问题类型】问题描述 原代码:... 建议修改:... """ ) .user("请review这段代码\n" + code).call().content(); } }
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 GET http://localhost:8080/review?code=int+a+%3D+1%2F0 HTTP/1.1 200 Content-Type : text/plain;charset=UTF-8Content-Length : 441Date : Tue, 24 Mar 2026 09:03:53 GMT【问题类型】潜在的运行时错误 原代码: ```java int a = 1 /0 ``` 建议修改: ```java try { int a = 1 / 0 } catch (ArithmeticException e) { System.err.println("除零错误" ) } ``` 解释:代码中尝试进行除零操作,这会导致 `ArithmeticException` 异常。通过使用 `try-catch` 块来捕获并处理这个异常,可以避免程序因未处理的异常而崩溃,并提供错误处理机制。 Response code: 200
PromptTemplate:模版化管理 实际项目中,Prompt 通常不是硬编码在 Java 代码中的,而是放在模版文件里管理。Spring AI提供了 PromptTemplate 来做这件事。
创建模版文件 src/main/resources/prompts/code-review.st(.st是 StringTemplate格式):
1 2 3 4 5 6 7 8 9 10 11 你是一个资深 {language} 工程师,有 {years} 年开发经验。 请 review 以下代码,找出: 1. bug和潜在风险2. 性能优化点3. 代码风格问题代码: {code} 请用中文回答,格式:每个问题独立列出,标注严重程度(高/中/低)。
在代码中使用模版 com/berial/springai/service/CodeReviewService.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.berial.springai.service;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.prompt.Prompt;import org.springframework.ai.chat.prompt.PromptTemplate;import org.springframework.core.io.ClassPathResource;import org.springframework.stereotype.Service;import java.util.Map;@Service public class CodeReviewService { private final ChatClient chatClient; public CodeReviewService (ChatClient.Builder builder) { this .chatClient = builder.build(); } public String review (String language, String code) { PromptTemplate template = new PromptTemplate ( new ClassPathResource ("/prompts/code-review.st" ) ); Prompt prompt = template.create(Map.of( "language" , language, "years" , "10" , "code" , code )); return chatClient.prompt() .call().content(); } }
Controller 调用 com/berial/springai/controller/prompt/TemplateCodeReviewController.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.berial.springai.controller.prompt;import com.berial.springai.service.CodeReviewService;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("/template") public class TemplateCodeReviewController { public final CodeReviewService codeReviewService; public TemplateCodeReviewController (CodeReviewService codeReviewService) { this .codeReviewService = codeReviewService; } @PostMapping("/review") public String review ( @RequestParam(defaultValue = "Java") String language, @RequestParam String code ) { return codeReviewService.review(language, code); } }
直接在代码利用模版字符串 com/berial/springai/SpringAiApplication.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.berial.springai.service;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.prompt.Prompt;import org.springframework.ai.chat.prompt.PromptTemplate;import org.springframework.stereotype.Service;import java.util.Map;@Service public class TranslateService { public final ChatClient chatClient; public TranslateService (ChatClient.Builder builder) { this .chatClient = builder.build(); } public String translate (String text, String targetLanguage) { PromptTemplate template = new PromptTemplate (""" 请将下面这段文字翻译成 {targetLanguage}, 保持原文的语气和风格,不要意译: {text} """ ); Prompt prompt = template.create(Map.of( "targetLanguage" , targetLanguage, "text" , text )); return chatClient.prompt(prompt).call().content(); } }
Controller 调用:
com/berial/springai/controller/prompt/TranslateController.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.berial.springai.controller.prompt;import com.berial.springai.service.TranslateService;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("/translate") public class TranslateController { public final TranslateService translateService; public TranslateController (TranslateService translateService) { this .translateService = translateService; } @GetMapping public String translate ( @RequestParam String text, @RequestParam(defaultValue = "英文") String targetLanguage ) { return translateService.translate(text, targetLanguage); } }
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 GET http://localhost:8080/translate? text=我今天学习了,很开心& targetLanguage=日文 GET http://localhost:8080/translate?text=%E6%88%91%E4%BB%8A%E5%A4%A9%E5%AD%A6%E4%B9%A0%E4%BA%86%EF%BC%8C%E5%BE%88%E5%BC%80%E5%BF%83&targetLanguage=%E6%97%A5%E6%96%87 HTTP/1.1 200 Content-Type : text/plain;charset=UTF-8Content-Length : 42Date : Tue, 24 Mar 2026 09:32:50 GMT今日、何かを学び、とても高兴 Response code : 200 ; Time : 1189ms (1 s 189 ms); Content length: 14 bytes (14 B)
ChatClient 的内联模版变量 ChatClient 的 .system() 和 .user() 都支持模版变量,不需要单独创建 PromptTemplate:
com/berial/springai/service/InlineTemplateService.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.berial.springai.service;import org.springframework.ai.chat.client.ChatClient;import org.springframework.stereotype.Service;@Service public class InlineTemplateService { private final ChatClient chatClient; public InlineTemplateService (ChatClient.Builder builder) { this .chatClient = builder.build(); } public String ask (String role, String domain, String concept) { return chatClient.prompt() .system(s -> s.text("你是一个 {role},擅长 {domain} 领域的问题" ) .param("role" , role) .param("domain" , domain)) .user(u -> u.text("请解释 {concept} 的工作原理" ) .param("concept" , concept)) .call().content(); } }
Controller调用:
com/berial/springai/controller/prompt/InlineTemplateController.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.berial.springai.controller.prompt;import com.berial.springai.service.InlineTemplateService;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("/inline") public class InlineTemplateController { private final InlineTemplateService inlineTemplateService; public InlineTemplateController (InlineTemplateService inlineTemplateService) { this .inlineTemplateService = inlineTemplateService; } @GetMapping("/ask") public String ask ( @RequestParam String role, @RequestParam String domain, @RequestParam String concept ) { return inlineTemplateService.ask(role, domain, concept); } }
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 GET http://localhost:8080/inline/ask? role=资深安全研究员& domain=IOT& concept=物联网漏洞挖掘 GET http://localhost:8080/inline/ask?role=%E8%B5%84%E6%B7%B1%E5%AE%89%E5%85%A8%E7%A0%94%E7%A9%B6%E5%91%98&domain=IOT&concept=%E7%89%A9%E8%81%94%E7%BD%91%E6%BC%8F%E6%B4%9E%E6%8C%96%E6%8E%98 HTTP/1.1 200 Content-Type : text/plain;charset=UTF-8Content-Length : 2150Date : Tue, 24 Mar 2026 09:45:40 GMT物联网漏洞挖掘是安全研究中一个重要的环节,其目的是为了识别和评估物联网设备的安全性。物联网漏洞挖掘涉及对物联网设备的硬件、固件、软件和网络连接等多个方面的深入分析,以发现潜在的安全漏洞。以下是其工作原理的一些关键步骤和概念: 1. **目标设备的识别与选择** :首先,需要确定要进行漏洞挖掘的物联网设备。这些设备可能包括智能恒温器、智能电视、智能家居设备等。2. **信息收集与分析** :对选定设备进行信息收集,包括设备的型号、固件版本、网络配置等。通过网络抓包、日志分析等方式获取更多关于设备运行状态的信息。3. **漏洞扫描与识别** :使用自动化工具和手动技术进行漏洞扫描。自动化工具可以帮助发现常见的安全问题,如弱密码、未加密的数据传输等。手动技术则可以让研究人员更深入地了解设备的工作原理,发现更隐蔽的漏洞。4. **渗透测试** :模拟攻击者的行为,尝试利用发现的漏洞进行攻击。这一步骤需要在法律允许的范围内进行,并且通常需要得到设备所有者的授权。5. **漏洞分析与评估** :对发现的漏洞进行详细分析,评估其潜在的影响。这包括考虑漏洞被利用的可能性、可能造成的危害程度等。6. **修复建议与报告** :基于分析结果,提出修复建议,并撰写详细的漏洞报告。报告通常会包括漏洞的详细描述、利用方法、影响范围以及建议的修复措施。7. **验证修复效果** :在设备制造商或所有者采取修复措施后,进行验证测试,确保漏洞已被有效修复。8. **持续监控与更新** :物联网设备的安全性是一个持续的过程,需要定期进行漏洞挖掘和评估,以确保设备始终处于安全状态。进行物联网漏洞挖掘时,重要的是要遵守相关的法律法规,确保所有的操作都在合法合规的前提下进行。此外,保护好被测试设备的隐私和数据安全也是非常重要的。 Response code: 200; Time: 15284ms (15 s 284 ms); Content length: 766 bytes (766 B)
完整示例:多场景 Prompt 服务 Service层:
com/berial/springai/service/PromptDemoService.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package com.berial.springai.service;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.prompt.Prompt;import org.springframework.ai.chat.prompt.PromptTemplate;import org.springframework.stereotype.Service;import java.util.Map;@Service public class PromptDemoService { private final ChatClient chatClient; public PromptDemoService (ChatClient.Builder builder) { this .chatClient = builder.build(); } public String codeReview (String language, String code) { return chatClient.prompt() .system(s -> s.text(""" 你是一个资深 {language} 工程师,做 code review。 找出 Bug 性能问题、代码风格问题,每个问题标注严重程度(高/中/低)。 """ ) .param("language" , language)) .user(u -> u.text("请 review 这段代码:\n```\n{code}\n```" ) .param("code" , code)) .call().content(); } public String translate (String text, String targetLanguage) { PromptTemplate promptTemplate = new PromptTemplate (""" 将下面的文字翻译成 {language} ,保持原文语气,不要意译: {text} """ ); Prompt prompt = promptTemplate.create(Map.of( "language" , targetLanguage, "text" , text )); return chatClient.prompt(prompt).call().content(); } public String domainQA (String domain, String question) { return chatClient.prompt() .system(s -> s.text(""" 你是一个 {domain} 领域的专家顾问。 只回答和 {domain} 相关的问题,其他问题拒绝回答。 不确定和内容要明确说明,不要编造。 """ ) .param("domain" , domain)) .user(question) .call().content(); } }
Controller层:
com/berial/springai/controller/prompt/MultiScenePromptController.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package com.berial.springai.controller.prompt;import com.berial.springai.service.PromptDemoService;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("/multi-scene") public class MultiScenePromptController { private final PromptDemoService promptDemoService; public MultiScenePromptController (PromptDemoService promptDemoService) { this .promptDemoService = promptDemoService; } @GetMapping("/review") public String review ( @RequestParam(defaultValue = "Java") String language, @RequestParam String code ) { return promptDemoService.codeReview(language, code); } @GetMapping("/translate") public String translate ( @RequestParam String text, @RequestParam(defaultValue = "英文") String targetLanguage ) { return promptDemoService.translate(text, targetLanguage); } @GetMapping("/qa") public String qa ( @RequestParam String domain, @RequestParam String question ) { return promptDemoService.domainQA(domain, question); } }