Prompt 工程实践

Prompt 工程实践

Berial Pwn

前言

​ 把 ChatClient 的用法过了一遍之后,可以单独看一下 Prompt,分析写什么样子的 Prompt 才能让模型更好的干活(虽然可能都被讲烂了)。

System Prompt 和 User Prompt 的职责

​ 在项目中很多人一开始就把所用东西都塞入 user 消息中,但这不是最好的做法。

System Prompt 是给模型的“岗位说明书”,定义:

  • 模型需要扮演什么角色
  • 有哪些能力限制
  • 输出格式
  • 遇到某种情况需要如何处理

User Prompt 是每次用户发送的具体问题,内容是变化的。

​ 好的做法是把稳定的、全局的约束放在 System Prompt,把动态的、每次不同的内容放在 User Prompt

System Prompt 的写法:五要素

  1. 角色(Role)
    告诉模型它是谁:
    你是一个资深的安全研究员,有10年的漏洞挖掘经验。

    角色的设定会影响模型的输出风格、用词和视角。技术问题用技术专家,客服问题用客服助手。

  2. 人物(Task)
    明确它要做什么:
    你的任务是帮助用户分析漏洞代码,找出代码中的逻辑漏洞或危险函数等

  3. 约束(Constraint)
    告诉它不能够做什么,或者有什么限制:
    只回答Pwn方向相关的问题,其他问题一律回复“这个问题超出我的服务范围”

    回答时不要做过度解释,直接给出结论和解决代码

  4. 输出格式(Format)
    告诉它使用什么格式输出
    回答使用 Markdown 格式,关键词加粗
    举例使用有序或无序列表

  5. 示例(Example)
    给几个正确的例子,特别是当任务比较特殊时:

Spring AI 中写 System Prompt(System和defaultSystem)

直接写在 Builder 里(对该实例全局生效)

​ 在 serviceController 的构造方法里,用 defaultSystemSystem 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-8
Content-Length: 883
Date: 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: 6964ms (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-8
Content-Length: 441
Date: 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; Time: 8589ms (8 s 589 ms); Content length: 257 bytes (257 B)

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-8
Content-Length: 42
Date: 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-8
Content-Length: 2150
Date: 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();
}

/* code review */
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();
}

/* Translate */
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();
}

/* domain */
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);
}
}
  • 标题: Prompt 工程实践
  • 作者: Berial
  • 创建于 : 2026-03-24 18:06:26
  • 更新于 : 2026-03-24 18:12:04
  • 链接: https://berial.cn/posts/Prompt_工程实践.html
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论