--- title: "07-提示词工程实践指南" created: 2026-05-13 aliases: - 提示词工程实践指南 tags: - 项目 --- # 提示词工程实践指南 和大模型打交道,提示词的质量直接决定输出效果。同样一个问题,换个问法,结果可能天差地别。 前面的章节聊过提示词的基础知识,这篇文章专门讲讲在Spring AI中怎么把这些技巧落地。 ## 系统提示词与用户提示词 Spring AI遵循OpenAI的设计规范,把提示词分成两类: - **系统提示词(System)**:设定AI的角色、行为准则、回答风格 - **用户提示词(User)**:具体的问题或指令 ![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/06-SpringAI详细解析/assets/FgBEth1abjK4hpeBDeBYf7dM_vNC-4625cd98.png]] ### 代码示例 在ChatClient中设置这两类提示词非常直观: ```java @RestController @RequestMapping("/prompt") public class PromptController { private final ChatClient chatClient; @GetMapping("/interview") public Flux mockInterview(@RequestParam String question) { return chatClient.prompt() // 系统提示词:定义角色 .system("你是一位资深的Java技术面试官,有10年面试经验。" + "回答问题时要专业严谨,适当追问细节," + "对模糊的回答要指出不足之处。") // 用户提示词:具体问题 .user(question) .stream() .content(); } } ``` ### 两种设置方式 **方式一:构建ChatClient时设置默认值** ```java @Bean public ChatClient chatClient(ChatModel chatModel) { return ChatClient.builder(chatModel) .defaultSystem("你是电商平台的智能导购助手," + "擅长根据用户需求推荐商品," + "回答要简洁实用。") .build(); } ``` 这样每次调用都会自动带上这个系统提示词。 **方式二:调用时动态设置** ```text // 调用时的system会覆盖默认设置 chatClient.prompt() .system("你现在是一个严格的代码审查员") .user("请审查这段代码:" + code) .call() .content(); ``` 注意:如果使用的是Prompt对象中的SystemMessage,行为会有所不同——它是**追加**而不是覆盖: > - `chatClient.prompt().system("...")` — **覆盖**默认的系统提示词 > - 通过 `Prompt` 对象传入 `SystemMessage` — **追加**到现有系统提示词之后 > > 使用错误的方式可能导致系统提示词叠加,产生意料之外的模型行为。 ```java // 这种方式是追加,不是覆盖 Prompt prompt = new Prompt( List.of( new SystemMessage("补充说明:重点关注安全性问题"), new UserMessage("分析这段代码") ) ); ``` ## Few-shot:示例教学法 Few-shot是提示词工程中非常实用的技巧:在提示词里给几个示例,让模型学会处理模式。 ### 场景:用户输入改写 假设我们要做一个搜索query优化功能,把用户的随意输入改写成规范的搜索词: ```java @GetMapping("/rewrite") public String rewriteQuery(@RequestParam String input) { return chatClient.prompt() .system(""" 你是一个搜索词优化专家,负责将用户的随意输入改写成更精准的搜索词。 改写规则: 1. 修正错别字 2. 去掉口语化表达,提取核心关键词 3. 补充必要的限定词 参考示例: 输入:想买个笔记本,玩游戏用的,预算一万左右 输出:游戏笔记本电脑 10000元价位 输入:苹果手机最新款多少钱 输出:iPhone 15 Pro Max 价格 输入:有没有好看的连衣裙推荐啊 输出:连衣裙 女装 时尚款 请直接输出改写后的搜索词,不需要解释。 """) .user(input) .call() .content(); } ``` 通过几个示例,模型就能学会你期望的改写风格。 > Few-shot(少样本示例)是提示词工程中最有效的技巧之一。与其用长篇文字描述期望的行为,不如直接给出输入-输出的示例对,让模型"看懂"你的意图。示例越典型,模型的表现越稳定。 ### 场景:格式化输出 Few-shot也很适合教模型特定的输出格式: ```java @GetMapping("/extract") public String extractInfo(@RequestParam String text) { return chatClient.prompt() .system(""" 从文本中提取结构化信息,按JSON格式输出。 示例1: 输入:张三,男,1990年出生,在阿里巴巴做程序员 输出:{"name":"张三","gender":"男","birthYear":1990,"company":"阿里巴巴","role":"程序员"} 示例2: 输入:李四是腾讯的产品经理,女,85年的 输出:{"name":"李四","gender":"女","birthYear":1985,"company":"腾讯","role":"产品经理"} 请严格按照示例格式输出JSON,不要添加其他内容。 """) .user(text) .call() .content(); } ``` ## 指定输出格式 很多时候我们需要大模型按特定格式输出,方便后续程序处理。 ### 直接在提示词中约定 ```python @GetMapping("/product-list") public Flux generateProductList(@RequestParam String category) { return chatClient.prompt() .system("你是一个商品数据生成器") .user(""" 请为"%s"类目生成3个虚构的商品信息。 要求以JSON数组格式输出,每个商品包含以下字段: - productId: 商品ID(字符串) - name: 商品名称 - price: 价格(数字,单位元) - description: 一句话描述 直接输出JSON,不要包含markdown代码块。 """.formatted(category)) .stream() .content(); } ``` ### 限制回答格式 有时候需要AI用特定的方式组织回答: ```java @GetMapping("/analyze") public String analyzeIssue(@RequestParam String issue) { return chatClient.prompt() .system(""" 你是一位技术问题分析专家。分析问题时请按以下结构回答: 【问题定位】 简要说明问题的本质 【可能原因】 用编号列出可能的原因 【排查步骤】 给出具体的排查步骤 【解决方案】 给出推荐的解决方案 """) .user(issue) .call() .content(); } ``` ## 思维链:让AI一步步思考 Chain-of-Thought(思维链)是一种让模型"展示推理过程"的技巧,对复杂问题特别有效。 ### 基本用法 只需要加一句"请一步一步思考": ```java @GetMapping("/calculate") public String calculate(@RequestParam String problem) { return chatClient.prompt() .system("你是一个数学老师,擅长解决应用题") .user(problem + "\n\n请一步一步思考,展示推理过程,最后给出答案。") .call() .content(); } ``` ### 更精细的控制 可以明确指定思考步骤: ```java @GetMapping("/design") public String designSolution(@RequestParam String requirement) { return chatClient.prompt() .system("你是一位系统架构师") .user(""" 请为以下需求设计技术方案: %s 请按照以下步骤分析: 步骤1:分析需求的核心目标 步骤2:识别技术挑战和约束条件 步骤3:列举可选的技术方案 步骤4:对比各方案的优缺点 步骤5:给出推荐方案和理由 """.formatted(requirement)) .call() .content(); } ``` ## PromptTemplate:模板化管理 当提示词比较复杂,或者需要动态替换部分内容时,PromptTemplate就派上用场了。 ### 基本使用 PromptTemplate使用`{变量名}`作为占位符: ```java @GetMapping("/recommend") public Flux recommend(@RequestParam String category, @RequestParam String budget) { PromptTemplate template = new PromptTemplate( "请推荐{count}个{category}领域的产品,预算在{budget}以内。" + "要求给出产品名称、特点和适用场景。" ); // 填充变量 Prompt prompt = template.create(Map.of( "count", "3", "category", category, "budget", budget )); return chatClient.prompt(prompt) .stream() .content(); } ``` ### Builder模式 更优雅的写法是用Builder: ```java @GetMapping("/review") public String codeReview(@RequestParam String code, @RequestParam String language) { Map variables = new HashMap<>(); variables.put("language", language); variables.put("code", code); PromptTemplate template = PromptTemplate.builder() .template(""" 请审查以下{language}代码,从以下几个方面给出建议: 1. 代码规范性 2. 潜在的bug 3. 性能问题 4. 安全隐患 """) .variables(variables) .build(); return chatClient.prompt(template.create()) .call() .content(); } ``` ### 自定义分隔符 默认用大括号`{}`作为占位符,如果和内容冲突,可以换成其他符号: ```java PromptTemplate template = PromptTemplate.builder() .renderer(StTemplateRenderer.builder() .startDelimiterToken('<') .endDelimiterToken('>') .build()) .template("请介绍的核心概念") .build(); String prompt = template.render(Map.of("topic", "微服务架构")); ``` ## 配置文件外置提示词 实际项目中,把提示词硬编码在Java代码里不是好习惯。更好的做法是放到配置文件中统一管理。 ### 创建.st模板文件 在resources目录下创建提示词模板文件,比如`prompts/product-recommend.st`: ```text 请为用户推荐{category}类目的商品。 用户偏好: - 预算范围:{budgetMin}到{budgetMax}元 - 使用场景:{scenario} 要求: 1. 推荐3-5个商品 2. 说明推荐理由 3. 给出购买建议 ``` ### 在代码中引用 ```java @RestController @RequestMapping("/shop") public class ShopController { // 注入模板文件内容 @Value("classpath:prompts/product-recommend.st") private Resource promptTemplate; private final ChatClient chatClient; @GetMapping("/recommend") public String recommend(@RequestParam String category, @RequestParam int budgetMin, @RequestParam int budgetMax, @RequestParam String scenario) { Map variables = Map.of( "category", category, "budgetMin", budgetMin, "budgetMax", budgetMax, "scenario", scenario ); PromptTemplate template = PromptTemplate.builder() .resource(promptTemplate) .variables(variables) .build(); return chatClient.prompt(template.create()) .call() .content(); } } ``` ### 这样做的好处 - **便于维护**:修改提示词不需要改代码、重新编译 - **职责分离**:提示词可以由产品或运营人员调整 - **版本管理**:提示词文件可以独立进行版本控制 - **多环境支持**:不同环境可以用不同的提示词 ## 最佳实践总结 经过这么多项目实践,总结几条提示词工程的经验: - **角色设定要具体**:说明职位、经验年限、擅长领域、回答风格,越具体越好 - **给出清晰的约束条件**:字数、格式、风格、禁忌项都要明确说明 - **用示例消除歧义**:当你觉得描述不清楚时,给个例子往往比长篇解释更有效 - **分步骤处理复杂任务**:让模型按步骤思考,能显著提升复杂任务的质量 - **迭代优化**:提示词不是一次就能写好的,先跑起来,看效果,不断调整 ## 小结 这篇文章介绍了Spring AI中提示词工程的实践方法: - **系统提示词和用户提示词**的分工与配合 - **Few-shot示例**让模型学会特定模式 - **指定输出格式**方便程序解析 - **思维链**提升复杂问题的推理质量 - **PromptTemplate**实现模板化管理 - **配置文件外置**提升可维护性 提示词工程是个需要积累的技能,多尝试、多总结,你的提示词会越写越好。 下一篇我们来聊聊结构化输出,看看怎么让大模型直接返回Java对象。 --- **企业级项目导航**:⬅️ [[06-对话记忆系统设计|06-对话记忆系统设计]] | 07-提示词工程实践指南 | ➡️ [[08-结构化输出深度剖析|08-结构化输出深度剖析]]