上一篇我们掌握了 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 官方文档。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐