Prompt 工程实战:五要素框架与 Spring AI 模板化落地

Prompt 工程实战:五要素框架与 Spring AI 模板化落地

Prompt 不是随便写一句话发给模型就完事------它直接决定了大模型能力的上限。本文从一个真实的 Code Review 场景出发,演示"差 Prompt"与"好 Prompt"的效果差距,提炼出 Prompt 五要素框架(角色、任务、约束、格式、示例),并通过 Spring AI 的多种模板化手段将其工程化落地。面向有 Spring Boot 基础、正在用 Spring AI 做大模型应用开发的后端工程师。


一、Prompt 为什么决定模型能力上限

很多人对 Prompt 的第一印象是"随便写两句话丢给 AI"。但在真实项目中你会发现,同一个模型、同一段输入,Prompt 写法不同,输出质量可以差出一个数量级

1.1 一个 Code Review 的对比实验

假设有这样一段代码:

java 复制代码
for (int i = 0; i < 1000; i++) {
    User user = userDao.findById(ids.get(i));
    user.setName("test");
    userDao.save(user);
}

Prompt A(随手写)

帮我看这段代码有没有问题。

模型返回:循环里 findById 如果返回 null,后续 setName 会抛空指针。然后就没了------只挖出一个问题

Prompt B(精心设计)

你是一个资深 Java 工程师,专注于代码质量和性能优化,有 10 年实战经验。请 review 这段代码,重点检查:1. 性能问题 2. 潜在 Bug 3. 最佳实践违反 4. 代码风格问题。每个问题标注严重程度:严重 / 中等 / 建议。

模型返回:N+1 查询问题(严重)、空指针风险(严重)、硬编码问题(中等)、方法命名不规范(建议)------挖出四个问题,还带了分级和修改建议

1.2 差距产生的根本原因

大模型的本质是一个概率生成器------它什么都知道,但不知道你需要什么。这跟一个刚入职的技术大牛很像:能力很强,但不了解业务上下文,你给的指令越模糊,他越容易"自由发挥"。

一个好的 Prompt 本质上在做三件事:

动作 作用 对应上面的例子
建立上下文 激活模型特定知识领域 "你是资深 Java 工程师"
明确目标 给模型一份工作清单 "重点检查性能、Bug、最佳实践、风格"
约束输出 规定格式,减少噪音 "标注严重程度:严重/中等/建议"

理解了这三件事,就可以引出更系统化的方法论了。


二、Prompt 五要素框架

告别"凭感觉写 Prompt",用工程化思维来拆解。一个高质量的 Prompt 由五个要素组成:

2.1 角色(Role)------激活特定知识域

角色的作用是告诉模型"用什么身份来思考"。不同的角色设定会激活模型不同的知识区域和表达风格。

最佳配方:职业身份 + 专业方向 + 经验背景

复制代码
你是一个资深 Java 后端工程师,有 10 年 Spring Boot 开发经验,专注于高并发系统架构和性能优化。

注意:写"你是全中国最厉害的 Java 大神"这种虚的没有用。模型需要的是有具体方向和经验维度的锚点,不是夸张修辞。

同一个问题,不同角色的回答深度完全不同

无角色 角色 = 初学者讲师 角色 = 性能优化专家
"数据库索引是一种数据结构......"(教科书式回答) "想象一本书的目录,有目录直接翻到,没目录就得一页页找" "索引本质是 B+Tree / Hash 结构,将全表扫描 O(n) 降至 O(log n),关键在于减少回表次数"

2.2 任务(Task)------给模型工作清单

任务是五要素中唯一一个必填项 。写法公式:动词 + 对象 + 期望结果

复制代码
❌ 处理一下这段代码
✅ Review 这段代码,找出潜在的空指针异常,给出具体位置和修复方案

拆步骤效果更好------模型会严格按步骤推进,输出更完整:

复制代码
第一步:判断代码中是否存在空指针风险
第二步:提取性能瓶颈点
第三步:给出每个问题的修复建议

2.3 约束(Constraint)------限定边界,防止跑偏

不加约束的模型就像一匹脱缰的马:你问苹果好不好吃,它能给你写一篇苹果产地分布的论文。

常见的约束类型:

约束类型 示例
范围约束 遇到与产品无关的问题,回复"超出服务范围"
长度约束 回答控制在 200 字以内,代码不超过 30 行
知识边界 不确定的内容说"我不确定,建议查阅文档",不要编造
风格约束 用口语化/学术/幽默的风格回答

最后一条尤其重要:不告诉模型"不知道就说不知道",它一定会编一个答案出来。在生产环境中,编造是最危险的行为。

2.4 格式(Format)------控制输出结构

当模型的输出要被下游代码解析时,格式约束就变成了刚需:

  • JSON 格式:告诉模型按指定结构输出 JSON,不要包含额外内容
  • Markdown 格式:表格、列表、代码块
  • 纯文本格式:不要 Markdown 标记,输出自然流畅的中文(语音播报场景)

2.5 示例(Example)------用范例消除歧义

遇到以下情况时,示例比文字描述更高效:

  • 输出格式特殊,语言描述不清
  • 需要特定风格或语气
  • 分类任务中类别边界模糊
  • 模型反复理解错意图

单示例(One-Shot)

复制代码
将代码注释翻译成英文。
示例输入:获取用户列表
示例输出:Get user list

请翻译:根据ID查询订单

多示例(Few-Shot)

复制代码
判断评论所属问题类型:
- "快递三天还没到,太慢了" → 物流问题
- "收到商品有划痕,和图片不一样" → 商品质量
- "客服态度很差,问了半天没解决" → 服务问题

请判断:"包装都坏了,里面东西也破了"

2.6 五要素的优先级

并非所有场景都需要写满五个要素。根据场景做取舍:

场景 必须 建议 可选
简单问答 任务 约束 角色、格式、示例
智能客服 角色、任务、约束 格式 示例
数据提取 任务、格式 约束 角色、示例
内容创作 任务、格式 角色、示例 约束
代码生成 任务、约束 角色、格式 示例

三、Prompt 工程 vs 微调:什么时候够用

在动手写代码之前,先回答一个常见困惑:Prompt 工程和模型微调该选哪个?

需求 Prompt 能搞定? 说明
特定风格回答 System Prompt 直接约束
输出 JSON 格式约束 + 结构化输出
公司私有知识 挂 RAG 不需要微调,检索增强即可
始终用特定语言 System Prompt 约束
掌握行业术语(少量) 通过示例注入
掌握行业术语(大量) 需要微调 Prompt 塞不下
学会全新任务形式(复杂) 需要微调 Few-Shot 覆盖不了

结论:大多数场景 Prompt 工程就够了,迭代成本低、见效快。优先 Prompt,不够再考虑 RAG,最后才上微调。


四、Spring AI 中的 Prompt 工程化实战

理论到此为止,下面全部是代码。Spring AI 提供了三种 Prompt 模板化手段,从简单到灵活逐级演进。

4.1 硬编码 Prompt------快速验证

最直接的方式:system 和 user 消息都写在代码里。

java 复制代码
@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();
    }
}

这里的 System Prompt 包含了角色(资深 Java 工程师)、任务(找 Bug/性能/最佳实践)、格式(问题类型+描述+建议),三个要素一次到位。

优点 :简单直接,适合原型验证。
缺点:Prompt 散落在 Controller 里,改一个字就要重新编译部署。

4.2 内联模板变量------一套模板覆盖多场景

Spring AI 的 ChatClient 原生支持 {variable} 占位符,运行时动态填充:

java 复制代码
@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();
    }
}

调用时传不同参数,一个接口覆盖所有场景:

复制代码
GET /inline/ask?role=DBA&domain=数据库&concept=索引
GET /inline/ask?role=架构师&domain=微服务&concept=服务熔断

同样的思路用在 Code Review 上

java 复制代码
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();
}

language=Java 它是 Java 工程师,传 language=Python 它就是 Python 工程师。角色和任务都实现了参数化

4.3 PromptTemplate + 外部文件------Prompt 与代码彻底解耦

当 Prompt 变长、需要非开发人员(比如产品经理)维护时,把 Prompt 抽到外部文件是更好的选择。

第一步 :在 src/main/resources/prompts/ 下创建模板文件 code-review.st

复制代码
你是一个资深 {language} 工程师,有 {years} 年开发经验。

请 review 以下代码,找出:
1. Bug 和潜在风险
2. 性能优化点
3. 代码风格问题

代码:
{code}

请用中文回答,格式:每个问题独立列出,标注严重程度(高/中/低)。

第二步 :Service 层通过 PromptTemplate 加载:

java 复制代码
@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(prompt)
                .call()
                .content();
    }
}

第三步:Controller 只负责接参数和调 Service:

java 复制代码
@RestController
@RequestMapping("/template")
public class TemplateCodeReviewController {

    private 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);
    }
}

这种方式的核心优势:

  • Prompt 文件可以热更新(配合配置中心),不用重启应用
  • 产品或运营人员可以直接编辑 .st 文件,不碰 Java 代码
  • 支持版本管理code-review-v1.stcode-review-v2.st,做 A/B 测试时切换文件名即可

4.4 defaultSystem------全局角色 + 约束注入

对于某个 ChatClient 实例的所有调用都需要遵守的规则,用 defaultSystem 一次性注入:

java 复制代码
@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();
    }
}

注意这段 defaultSystem 的结构------它把角色、任务、约束、格式四个要素全部写进了 System Prompt。后续每次调用只需要传 user 消息,模型就会在这套规则下工作。这是五要素框架在 Spring AI 中最典型的工程化落地方式

4.5 PromptTemplate 用于翻译场景

再看一个翻译场景,展示 PromptTemplate 处理纯用户消息(不区分 system/user)的用法:

java 复制代码
@Service
public class TranslateService {

    private 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();
    }
}

这里的约束是"保持原文语气和风格,不要意译"------简单的一句话就把模型限定在了直译路线上。

4.6 三种模板化方式对比

方式 Prompt 存放位置 适用场景 可维护性
硬编码 Java 代码中 原型验证、简单场景
内联模板变量 Java 代码中(参数化) 多场景复用
PromptTemplate + 外部文件 .st 资源文件 生产环境、需要非开发人员维护

五、常见踩坑与最佳实践

在实际项目中,以下几个问题出现频率最高。

5.1 任务描述太模糊

复制代码
❌ 帮我优化这段代码
✅ 帮我优化这段代码的查询性能,只改查询逻辑,不改接口签名,使用 Java Stream API

不给方向,模型可能从可读性、性能、内存、命名各个角度都改一遍,对项目的改动超出预期。

5.2 所有内容塞进一条 User Prompt

复制代码
❌ user: "你是一个代码助手,只回答技术问题,用中文回答,代码用 Java 17 语法。帮我解释 Spring AOP 是什么。"
✅ system: "你是一个代码助手,只回答技术问题,用中文回答,代码用 Java 17 语法。"
   user: "帮我解释 Spring AOP 是什么。"

System Prompt 和 User Prompt 的职责不同:system 管"你是谁、你怎么做",user 管"这次具体做什么"。混在一起会导致角色设定在多轮对话中丢失。

5.3 没有约束输出格式

让模型"分析文章优缺点",它可能给你写一篇散文。加上格式约束:

复制代码
优点(3 条):
缺点(3 条):
总体评分(1-10):
改进建议(1 条):

输出立刻变得可解析、可对比。

5.4 过度依赖模型猜测意图

复制代码
❌ 用中文写一篇 Spring Boot 的文章
✅ 用中文写一篇 Spring Boot 入门文章,2000 字左右,面向有 Java 基础的初学者,包含 3 个代码示例,语气轻松

字数、受众、深度、是否需要代码、语气风格------这些模型都能猜,但猜出来的一定不如你直接说清楚。


六、总结

本文的核心内容可以浓缩为三句话:

  1. Prompt 决定模型能力上限------不是模型不行,是你的指令不够精确。
  2. 五要素框架(角色、任务、约束、格式、示例)------把"凭感觉"变成可复制的工程方法。
  3. Spring AI 提供了从硬编码到外部模板文件的完整工具链------让 Prompt 管理融入正常的软件工程流程。

本系列后续内容

Prompt 工程解决了"怎么跟模型说话"的问题,但一个完整的 AI 应用远不止对话。后续将进入更深层的主题:

  • Agent 智能体实战:让大模型具备自主规划和执行能力,从"被动回答"升级为"主动做事"
  • MCP(Model Context Protocol)实战:标准化的工具调用协议,让 Agent 能够操作外部系统
  • RAG 检索增强生成:让模型接入企业私有知识库,解决"模型不知道公司内部信息"的问题

敬请关注。

相关推荐
CoderJia程序员甲1 小时前
GitHub 热榜项目 - 日榜(2026-03-18)
ai·大模型·llm·github·ai教程
东离与糖宝2 小时前
Java 21 虚拟线程与 AI 推理结合的最新实践
java·人工智能
WiSirius2 小时前
LLM:基于 AgentScope + Streamlit 的 AI Agent脑暴室
人工智能·深度学习·自然语言处理·大模型·llama
菜鸟小九3 小时前
hot100(71-80)
java·数据结构·算法
大傻^3 小时前
LangChain4j 1.4.0 快速入门:JDK 11+ 基线迁移与首个 AI Service 构建
java·开发语言·人工智能
代码探秘者3 小时前
【大模型应用】4.分块之六大策略
java·数据结构·后端·python·spring
码喽7号3 小时前
Springboot学习六:MybatisPlus的多表查询以及分页查询
java·spring boot·学习
那我掉的头发算什么3 小时前
【博客系统】基于Spring全家桶的博客系统(下)
java·后端·spring·mybatis·开发