提示词工程(下):思维链、自我一致与 Cursor 规则
上一篇我们掌握了 CO-STAR 框架,让 AI 能按你的期望风格输出。但当你面对复杂任务——比如数学推理、多步骤决策、代码逻辑分析时,光靠格式框架还不够。今天我们来解锁两个进阶技巧:思维链(Chain of Thought) 和 自我一致性(Self-Consistency),并学习如何用 Cursor Rules 把这些经验沉淀为团队规范。
这一篇不需要引入任何新依赖,所有实验都在现有的 Spring Boot 项目上进行。如果你还没有项目,回顾第一篇即可。
一、痛点场景:AI 为什么在复杂问题上“翻车”?
让我们先看一个真实的问题。你把下面的题目丢给 AI:
小明有 3 个苹果,小红给了小明 5 个苹果,小明又吃掉了 2 个。
现在小明有几个苹果?
对于人类,这是小学一年级的题目,答案一眼可知:3 + 5 - 2 = 6。但如果你只是简单地问模型“现在小明有几个苹果?”,它有时会直接给出错误答案——比如 8 个或者 4 个。原因在于:模型在“即兴回答”时可能跳过了中间步骤,凭“直觉”猜了一个数字。
这就是大语言模型的一个著名弱点:在处理多步骤推理或数学计算时,仅凭一次前向传播容易在中间逻辑链上出岔子。思维链(Chain of Thought,CoT) 正是为了弥补这个缺陷而生的技巧。它能迫使模型像人类一样“逐步推理”,大幅提升复杂问题的准确率。
今天除了思维链,我们还会学到它的“增强版”——自我一致性(Self-Consistency),以及如何用 Cursor Rules 把这些 Prompt 技巧保存为可复用的团队资产。
二、核心概念快览
2.1 思维链(Chain of Thought)
思维链(CoT)的核心思想是:在 Prompt 中显式要求模型在给出最终答案前,先输出中间的推理步骤。 这可以通过两种方式实现:
- Zero-shot CoT:不需要任何示例,直接在 Prompt 末尾加上一句“让我们一步一步思考(Let’s think step by step)”。
- Few-shot CoT:先给几个包含完整推理过程的示例,再提出真实问题,让模型模仿示例的推理模式。
研究表明,Zero-shot CoT 就足以在很多推理任务上将准确率提升 10%-30%。而 Few-shot CoT 在特定领域(如数学、代码分析)上还能进一步提升。
2.2 自我一致性(Self-Consistency)
思维链能让模型给出推理过程,但单次推理仍然可能出错。自我一致性是 CoT 的进一步增强:让同一个模型对同一个问题生成多条不同的推理路径,然后通过投票选出最一致的答案。
具体做法是:把温度(temperature)调高(比如 0.7 以上),让模型运行多次(比如 5 次),每次都可能输出不同但有推理过程的答案,最后统计出现次数最多的那个最终答案。这就像让 5 个专家各自独立解题,然后取他们最认同的结果——准确率通常高于任何单人。
2.3 Cursor Rules
Cursor Rules 是 Cursor IDE 提供的一项功能,允许你在项目中创建 .cursorrules 或 .cursor/rules/ 下的规则文件,用自然语言定义代码风格、命名约定、常用 Prompt 模板等。然后 Cursor 的 AI 助手在生成代码或回答问题时会自动遵守这些规则。
对我们今天的内容来说,Cursor Rules 的实际价值是:把 CO-STAR、思维链、少样本提示这些 Prompt 技巧保存为规则,让团队成员在任何对话中都能一键复用,而不需要每次都重新手打长 Prompt。
2.4 三者关系
思维链(CoT)
↓ 让模型展示推理过程
自我一致性(Self-Consistency)
↓ 多次推理 + 投票,提升准确率
Cursor Rules
↓ 把这些 Prompt 模板保存为团队规范
前两者是 Prompt 设计技巧,后者是工程化落地手段。
三、环境准备
无需任何新依赖。仍使用之前的 ChatClient 和 Spring Boot 项目。配置文件沿用即可。
为便于演示自我一致性的多次调用,我们可以直接在 Service 中用循环调用 ChatClient,所以不需要额外配置。如果想更方便地看多次调用结果,可以调整日志级别:
logging:
level:
com.example: DEBUG
四、代码实战
4.1 创建 ReasoningService
新建 ReasoningService,包含普通调用、思维链、自我一致性三种方法:
package com.example.springaihelloworld.service;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class ReasoningService {
private final ChatClient chatClient;
public ReasoningService(ChatClient chatClient) {
this.chatClient = chatClient;
}
/**
* 普通调用:不附加任何推理提示
*/
public String standardCall(String question) {
return chatClient
.prompt()
.user(question)
.call()
.content();
}
/**
* Zero-shot CoT:在问题末尾加上“让我们一步一步思考”
*/
public String zeroShotCoT(String question) {
String cotPrompt = question + "\n\n请一步一步思考,先写出推理过程,再给出最终答案。";
return chatClient
.prompt()
.user(cotPrompt)
.call()
.content();
}
/**
* Few-shot CoT:先给一个带完整推理过程的示例,再提问
*/
public String fewShotCoT(String question) {
String systemPrompt = """
你是一个擅长逐步推理的数学助手。
在回答用户的问题前,先写出完整的分步推理过程,最后一行以 "答案:" 开头给出最终答案。
示例:
问题:一个水果店上午卖出 12 个苹果,下午又卖出 8 个,晚上进货 15 个。原来有 30 个苹果,现在还剩多少个?
推理过程:
1. 计算总卖出数:12 + 8 = 20 个
2. 计算卖出后剩余:30 - 20 = 10 个
3. 晚上进货后:10 + 15 = 25 个
答案:25 个
现在,请回答用户问题。
""";
return chatClient
.prompt()
.system(systemPrompt)
.user(question)
.call()
.content();
}
/**
* 自我一致性(Self-Consistency):
* 用较高温度运行多次 CoT,然后投票选出最一致的答案
* @param question 用户问题
* @param times 运行次数(建议 3~5 次)
* @return 包含所有推理过程和投票结果的字符串
*/
public String selfConsistency(String question, int times) {
List<String> allAnswers = new ArrayList<>();
StringBuilder processLog = new StringBuilder();
for (int i = 0; i < times; i++) {
// 每次调用都使用相同的 Few-shot CoT Prompt,但温度较高以增加多样性
String cotPrompt = """
请一步一步推理,最后一行以 "答案:" 开头给出最终答案。
问题:""" + question;
String response = chatClient
.prompt()
.user(cotPrompt)
.call()
.content();
processLog.append("=== 第 ").append(i + 1).append(" 次推理 ===\n")
.append(response).append("\n\n");
// 提取答案行(以 "答案:" 开头的行)
String answer = extractAnswer(response);
if (answer != null) {
allAnswers.add(answer.trim());
}
}
// 统计答案频率,选出得票最多的
String bestAnswer = voteForAnswer(allAnswers);
return "【自我一致性投票结果】\n"
+ "总共运行 " + times + " 次,各次答案:\n"
+ allAnswers.stream()
.map(a -> " - " + a)
.collect(Collectors.joining("\n"))
+ "\n\n得票最多答案:**" + bestAnswer + "**\n\n"
+ "详细推理过程:\n" + processLog;
}
/**
* 从模型回复中提取 "答案:" 后面的内容
*/
private String extractAnswer(String response) {
if (response == null) return null;
for (String line : response.split("\n")) {
line = line.trim();
if (line.startsWith("答案:")) {
return line.substring("答案:".length()).trim();
}
if (line.startsWith("答案:")) {
return line.substring("答案:".length()).trim();
}
}
// 如果没找到,返回最后一行作为备选
String[] lines = response.split("\n");
return lines[lines.length - 1].trim();
}
/**
* 简单多数投票
*/
private String voteForAnswer(List<String> answers) {
if (answers.isEmpty()) return "无有效答案";
Map<String, Long> freq = answers.stream()
.collect(Collectors.groupingBy(a -> a, Collectors.counting()));
return freq.entrySet().stream()
.max(Map.Entry.comparingByValue())
.get()
.getKey();
}
}
关键点解读:
zeroShotCoT:直接在问题后追加一句“请一步一步思考”,无需示例,最简单但效果已经显著。fewShotCoT:System Prompt 中包含一个完整的示例(问题 → 推理过程 → 答案),引导模型先推理再给答案。selfConsistency:循环调用多次(默认用 1.0 的温度增加多样性),提取每次的答案,最后用简单多数投票决定最终答案。这是一种“用更多算力换取更高准确率”的策略,在数学和逻辑推理等确定性问题中特别有效。extractAnswer方法用于解析模型回复中的最终答案行,这是自我一致性投票的基础。如果模型输出格式不规范(例如没有“答案:”前缀),它会退回到使用最后一行作为答案,因此 Prompt 中的格式引导非常重要。
4.2 创建对比 Controller
新建 ReasoningController:
package com.example.springaihelloworld.controller;
import com.example.springaihelloworld.service.ReasoningService;
import org.springframework.web.bind.annotation.*;
@RestController
public class ReasoningController {
private final ReasoningService reasoningService;
public ReasoningController(ReasoningService reasoningService) {
this.reasoningService = reasoningService;
}
/**
* 普通调用
*/
@GetMapping("/reason/standard")
public String standard(@RequestParam String q) {
return reasoningService.standardCall(q);
}
/**
* Zero-shot 思维链
*/
@GetMapping("/reason/zeroshot")
public String zeroShot(@RequestParam String q) {
return reasoningService.zeroShotCoT(q);
}
/**
* Few-shot 思维链
*/
@GetMapping("/reason/fewshot")
public String fewShot(@RequestParam String q) {
return reasoningService.fewShotCoT(q);
}
/**
* 自我一致性(运行 5 次)
*/
@GetMapping("/reason/selfconsistency")
public String selfConsistency(@RequestParam String q) {
return reasoningService.selfConsistency(q, 5);
}
}
4.3 配置 Cursor Rules(保存 Prompt 模板)
Cursor Rules 不是 Spring AI 的代码,而是 Cursor IDE 的项目配置文件。我们可以在项目根目录下创建 .cursorrules 文件(或 .cursor/rules/ 目录下的多个 .mdc 文件),把今天学到的 Prompt 技巧写成规则,让 AI 助手在任何对话中都能自动遵守。
步骤:在项目根目录创建 .cursorrules 文件
touch .cursorrules
然后写入以下内容:
# 思维链规则
当用户询问数学、逻辑或需要多步推理的问题时,你必须:
1. 先写出完整的分步推理过程(Chain of Thought),每一步编号。
2. 推理结束后,单独一行以 "答案:" 开头给出最终答案。
3. 如果问题不确定,请说明假设条件。
## 示例
用户:小明有3个苹果,小红给了小明5个苹果,小明又吃掉了2个。现在小明有几个苹果?
助手:
推理过程:
1. 初始苹果数:3
2. 小红给后:3 + 5 = 8
3. 吃掉后:8 - 2 = 6
答案:6
# CO-STAR 规则(复用上一篇的技巧)
在回答任何需要结构化输出的问题时,请遵循 CO-STAR 框架:
- C(背景):先理解并复述用户的问题背景。
- O(目标):明确你要达成的目标。
- S(风格):回答要简洁、专业。
- T(语气):保持耐心、建设性。
- A(受众):面向技术初学者。
- R(响应格式):用 Markdown 的小标题和列表组织内容。
# 少样本提示规则
在需要特定格式输出时,我会先在 Prompt 中给出 2-3 个示例。请严格模仿示例的格式、语气和结构来生成回复。
保存后,Cursor 的 AI 助手在生成代码或回答问题时就会自动应用这些规则。比如你以后在 Cursor 的 Chat 中问“请帮我写一个计算斐波那契数列的函数”,AI 会先给出推理过程,再输出代码。这相当于把“思维链”技巧固化为你的团队默认行为。
注意:
.cursorrules文件是项目级别的,建议提交到 Git 仓库,整个团队共享。如果某些规则仅个人使用,可以在 Cursor 设置中配置“全局规则”。
五、运行与演示
5.1 启动项目
确认 API Key 已设置,启动 Spring Boot。
5.2 对比普通调用 vs 思维链
用同一个需要推理的问题,分别测试标准版和思维链版。
普通调用:
http://localhost:8080/reason/standard?q=小明有3个苹果,小红给了小明5个苹果,小明又吃掉了2个。现在小明有几个苹果?
可能返回:
小明现在有8个苹果。
(错误答案,模型直接“猜”了 3+5=8,忘记减去吃掉的 2 个。)
Zero-shot 思维链:
http://localhost:8080/reason/zeroshot?q=小明有3个苹果,小红给了小明5个苹果,小明又吃掉了2个。现在小明有几个苹果?
返回类似:
推理过程:
- 小明最初有 3 个苹果。
- 小红给了他 5 个,所以他有 3 + 5 = 8 个苹果。
- 然后他吃掉了 2 个,所以剩下 8 - 2 = 6 个苹果。
答案:6
Few-shot 思维链:
http://localhost:8080/reason/fewshot?q=一个水池有3个进水口,每个进水口每分钟进水2升,同时有一个排水口每分钟排水5升。5分钟后水池里有多少水?
返回类似:
推理过程:
1. 计算总进水量:3个进水口 × 2升/分钟 × 5分钟 = 30升
2. 计算总排水量:1个排水口 × 5升/分钟 × 5分钟 = 25升
3. 净水量:30 - 25 = 5升
答案:5升
可以看到,思维链让推理过程变得透明,且大幅降低了计算错误的概率。
5.3 测试自我一致性
用同一个问题,访问自我一致性接口:
http://localhost:8080/reason/selfconsistency?q=一个数加上5,再乘以3,结果是36。这个数是多少?
你会看到 5 次不同推理过程的完整记录,以及最终的投票结果。即使某一次推理出现了计算偏差,多数投票仍然会把正确答案(7)选出来。这就是自我一致性的价值:用多次采样的“民主”来抑制单次推理的“幻觉”。
5.4 验证 Cursor Rules
在配置了 .cursorrules 的 Cursor IDE 中,按 Cmd+L(Mac)或 Ctrl+L(Windows)打开 AI Chat,输入问题:
帮我写一个Java方法,计算两个整数的最大公约数。
你会看到 AI 助手先展示推理过程(选择辗转相除法),再给出代码。这就是规则文件在发挥作用。
六、常见问题与避坑提示
问题一:Zero-shot CoT 对某些问题效果不好
“让我们一步一步思考”对逻辑推理、数学计算类问题提升明显,但对简单的陈述性问题(如“什么是Java?”)意义不大,还可能让回复变得啰嗦。建议根据问题类型动态决定是否启用 CoT,可以在 System Prompt 中做条件判断,或者由用户手动选择。
问题二:自我一致性的调用次数怎么选?
理论上次数越多准确率越高,但成本也线性增加。对于关键决策场景(如自动化判卷、财务计算),建议 5~7 次;对于一般应用,3 次即可;学习阶段可先跑 3 次感受效果。超过 7 次后边际收益会明显递减。
问题三:自我一致性中如何提高答案多样性?
如果每次生成的答案完全一样,投票就没意义了。可以通过提高温度(0.7~1.0)来增加多样性。在我们的代码中可以在每次调用前动态设置,或者在配置文件中预设较高的 temperature。
// 在 selfConsistency 方法中每次调用前设置温度
chatClient
.prompt()
.user(cotPrompt)
.options(org.springframework.ai.openai.OpenAiChatOptions.builder()
.withTemperature(0.8) // 高温度增加多样性
.build())
.call()
.content();
问题四:Cursor Rules 不生效
- 确认文件名为
.cursorrules(注意前面的点),且位于项目根目录。 - 确保文件保存后,重启 Cursor 或重新打开项目。
- 在 Cursor 设置中检查是否启用了“Project Rules”功能。
- 规则文件中的内容需要用自然语言清晰描述,避免歧义。
问题五:思维链导致 Prompt 过长,成本增加
是的,思维链会让 Prompt 和回复都变长,Token 消耗相应增加。对于生产环境,可以考虑:
- 只在复杂任务上启用 CoT。
- 使用较短的 Zero-shot CoT 提示(就加一句话)而非 Few-shot 的长示例。
- 对简单问题直接走普通调用,通过路由机制分派(后续 Agent 工作流章节会讲到)。
七、小结与下一步预告
本篇回顾
- 理解了思维链(CoT)的原理和两种实现方式:Zero-shot(加一句话)和 Few-shot(给示例)。
- 掌握了自我一致性:多次推理 + 投票,用算力换取准确率。
- 学会了用 Cursor Rules 将 CO-STAR、思维链等 Prompt 技巧固化为项目规范,沉淀为团队资产。
动手建议
挑一个你项目中最容易出错的推理类 AI 功能,先用思维链改造它的 Prompt,再用自我一致性跑 3~5 次取投票结果,感受准确率的提升。然后为你的项目创建一个 .cursorrules 文件,把你觉得好用的 Prompt 技巧写进去。
下一步预告
提示词工程到这里告一段落。现在你的 AI 已经会说、会听、会推理了。下一篇,我们要让它“画”——图像生成初探:OpenAI 与千帆平台一键出图。我们会调用 Image Model API,分别接入 OpenAI 和百度千帆平台,实现文字描述生成图片的功能。把“看图说话”变成“说话出图”,精彩不容错过。
下一篇见。
本系列博客基于 Spring AI 1.1.6 版本编写。思维链和自我一致性为通用 Prompt 工程技巧,与具体模型和框架无关。不同模型对 CoT 的响应质量存在差异,建议在实际项目中针对所选模型进行充分测试。Cursor Rules 功能请参考 Cursor IDE 官方文档。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)