课程0基础Agent开发课 / Prompt工程 / 思维链进阶-Tree-of-Thoughts与Self-Consistency
— 20 min read

思维链进阶-Tree-of-Thoughts与Self-Consistency

> **本文适合谁**

思维链进阶:Tree of Thoughts 与 Self-Consistency

本文适合谁

已经熟悉基础 CoT、但在复杂推理任务上准确率仍不稳定的开发者。ToT 和自洽采样是 CoT 的进阶版,适合数学、逻辑分析、需要探索多条路径的任务。


标准 CoT 让模型在回答前写出推理步骤,效果比直接给答案好得多。但它有一个根本性的局限:推理是线性的,一旦某个中间步骤走偏,后续步骤会继续沿着错误的方向推进,最终给出一个"看起来有理有据但结论错误"的回答。

这种现象在数学题和多步逻辑推理中尤为明显。本章介绍两种进阶技术:Self-Consistency(自洽采样,让模型多次独立回答同一问题,取多数结果)和 Tree of Thoughts(树状思维,让模型主动探索多条推理路径并评估选优),它们用不同的方式解决这个问题。

标准 CoT 的局限

Self-Consistency (自一致性)
多路推理投票取最高频答案

同一问题
(多次采样)

推理链1 → 答案A

推理链2 → 答案B

推理链3 → 答案A

推理链4 → 答案A

推理链5 → 答案C

多数投票统计:答案A获得3票(最多)

最终答案:A

机制:多次采样 + 多数投票
适合:问答、常识推理、数学计算

Tree of Thought (ToT)
搜索树探索多条推理路径

问题 (Root)

路径A

A1

A2

路径B

B1

B1-1
最优解

路径C

X剪枝
PRUNE

评估函数打分:选最优路径
机制:BFS/DFS 搜索 + 剪枝
适合:规划类、数学推理、复杂决策

三种推理策略对比——CoT 线性推理、ToT 树状探索、Self-Consistency 多路投票,各有适用场景

一个具体例子说明问题所在。给模型一道推理题:

"John 有 5 个苹果,给了 Mary 2 个,Mary 又给了 Tom 1 个,Tom 原来有 3 个。现在 John 和 Tom 合计有多少个?"

标准 CoT 可能的推理路径:

code
1. John 原有 5 个,给了 Mary 2 个,John 剩 5-2=3 个  ✓
2. Mary 收到 2 个,又给了 Tom 1 个,Tom 得到 1 个  ✓
3. Tom 原来有 3 个,加上 1 个,Tom 有 3+1=4 个  ✓
4. John 有 3 个,Tom 有 4 个,合计 3+4=7 个  ✓

这道题 CoT 能正确处理。但如果题目更复杂,步骤更多,中间某步出错了,后续全部连锁错误,而模型不会回头检查——因为它生成 token 是单向的,没有"后悔"机制。

标准 CoT 的核心局限在于:它是一条不可回溯的单路径。就像走迷宫时只能向前走、不能退回来,一旦选错了路口,就会在错误的方向上越走越远。

Self-Consistency:多次采样取众数

统计原理:错误是发散的,正确是收敛的

Self-Consistency 的思路很直接:既然单次推理可能走偏,那就多推理几次,取最多人同意的答案。

这背后有一个重要的统计直觉:错误答案的分布是发散的,正确答案的分布是收敛的

想象一道数学题,正确答案是 42。可能出错的方式有很多——计算第一步时出错得到 38,算第三步时出错得到 51,误解题意得到 65……错误路径是多种多样的,每种错误各自出现的频率相对较低。但正确路径只有一条(或有限几条),通向唯一正确答案 42。

当你进行 N 次独立采样时(每次用稍高的 temperature 引入随机性),正确答案 42 会在多次采样中反复出现,而各种错误答案由于分散,单个错误的出现频率较低。通过投票取多数,就能以较高概率选出正确答案。

这个原理和**集成学习(Ensemble Learning)**中的 Bagging 策略非常类似:单个分类器的错误是随机的、不相关的,多个分类器的多数投票结果比单个分类器更可靠。

原理如下:用较高的 temperature(温度参数,值越高模型输出越随机多样)多次调用模型,得到多条独立的推理路径,各路径可能走不同的"弯路",但正确答案应该在多数路径里出现。错误的推理路径是发散的(有很多种错法),正确的推理路径是收敛的(答案是唯一的)——这是 Self-Consistency 有效的数学直觉。

问题

推理路径 1
答案:7

推理路径 2
答案:7

推理路径 3
答案:8 错误

推理路径 4
答案:7

推理路径 5
答案:7

投票

最终答案:7
得票 4/5

Self-Consistency 实现

python
import os
import re
from collections import Counter
from typing import Optional
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

llm = ChatOpenAI(
    model="deepseek-chat",
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com",
    # temperature 设高一点,让每次采样路径不同
    # 如果 temperature=0,每次结果一样,投票没有意义
    temperature=0.7,
)

cot_template = ChatPromptTemplate.from_messages([
    ("system", "你是一个精确的推理助手。请一步一步分析,最后给出答案。答案格式为:最终答案:[数字或结论]"),
    ("human", "{question}"),
])

def extract_answer(response: str) -> Optional[str]:
    """从推理结果中提取最终答案"""
    # 匹配"最终答案:xxx"或"答案是xxx"等模式
    patterns = [
        r"最终答案[::]\s*(.+?)(?:\n|$)",
        r"答案[是为][::]?\s*(.+?)(?:\n|$)",
        r"所以.{0,5}[是为有]\s*(.+?)(?:\n|$)",
    ]
    for pattern in patterns:
        match = re.search(pattern, response)
        if match:
            return match.group(1).strip()
    # 如果没有匹配到标准格式,取最后一行作为答案
    lines = [l.strip() for l in response.strip().split("\n") if l.strip()]
    return lines[-1] if lines else None

def self_consistency(question: str, num_samples: int = 5) -> dict:
    """
    Self-Consistency 推理:多次采样,取众数答案
    num_samples=5 是实践中常用的数量,再多收益递减但成本线性增加
    """
    answers = []
    reasoning_paths = []

    for i in range(num_samples):
        messages = cot_template.format_messages(question=question)
        response = llm.invoke(messages).content
        answer = extract_answer(response)

        reasoning_paths.append(response)
        if answer:
            answers.append(answer)

    # 统计各答案出现次数
    answer_counts = Counter(answers)
    majority_answer, majority_count = answer_counts.most_common(1)[0]

    return {
        "question": question,
        "majority_answer": majority_answer,
        "confidence": majority_count / num_samples,
        "answer_distribution": dict(answer_counts),
        "reasoning_paths": reasoning_paths,
        "num_samples": num_samples,
    }


# 验证目的:观察多次采样的答案分布,验证多数投票是否能纠正偶发错误
question = """
小明有24块糖,他给了小红1/3,小红又把自己的糖的一半给了小李。
如果小李原来有8块糖,现在小李有多少块糖?
"""

result = self_consistency(question, num_samples=5)
print(f"问题:{result['question'].strip()}")
print(f"最终答案:{result['majority_answer']}")
print(f"置信度:{result['confidence']:.0%}{result['num_samples']} 次采样中的多数)")
print(f"答案分布:{result['answer_distribution']}")

Self-Consistency 在数学推理任务上通常能提升 5-15% 的准确率,代价是 N 倍的 API 调用成本和延迟。num_samples=5 是一个常用的平衡点,再增加到 10 次,收益已经明显递减。

Tree of Thoughts:主动探索多条路径

ToT 的理论动机:允许回溯的树状搜索

Self-Consistency 改进了推理的结果质量(通过多次采样投票),但每条推理链本身仍然是线性的、不可回溯的。Tree of Thoughts 解决的是另一个层次的问题:推理过程本身就需要探索和回溯

把推理过程类比为路径搜索:

  • 标准 CoT 是深度优先、不允许回溯的单路径搜索——一旦选了一个方向就走到底
  • Self-Consistency 是多条独立的单路径并行搜索,最后投票
  • ToT 是真正的树状搜索——在每个决策点分叉,评估各分支的前景,优先探索有希望的分支,遇到死路可以回溯

ToT 适合的场景是那些"中途需要做出关键决策,而且早期决策错误会导致后续全部白费"的问题。比如写作规划(选错了大纲方向,写了一半发现不对,需要推翻重来)、代码调试(选错了假设的原因,debug 了半天走了弯路)、逻辑谜题(某步推断错误,后续全部崩溃)。

ToT 把问题分解为"思维步骤",每一步可以产生多个候选想法,再对这些想法进行评估(打分),选出最有前途的继续深入,剪掉明显错误的分支(类似棋类游戏中剪枝的策略,不值得探索的路径直接舍弃)。整个过程形成一棵树。

剪枝

问题

思路 A
得分: 8/10

思路 B
得分: 5/10

思路 C
得分: 3/10

深化 A1
得分: 9/10

深化 A2
得分: 4/10

深化 B1
得分: 6/10

放弃

最终答案

简化版 ToT 实现

完整的 ToT 需要实现树的数据结构和搜索算法(BFS,广度优先搜索,按层逐层展开所有节点;MCTS,蒙特卡罗树搜索,用随机模拟评估各路径价值再选优)。以下是一个简化但可运行的版本,演示核心思想(验证目的:验证"生成-评估-剪枝"的循环机制是否正常运作,以及最优路径的回溯是否正确):

python
import os
from dataclasses import dataclass, field
from typing import List, Optional
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

# 两个模型:一个生成想法,一个评估想法
# 实际场景中可以用同一个模型,也可以用强弱两个模型
generator_llm = ChatOpenAI(
    model="deepseek-chat",
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com",
    temperature=0.8,  # 生成时温度高,鼓励多样性
)

evaluator_llm = ChatOpenAI(
    model="deepseek-chat",
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com",
    temperature=0,  # 评估时温度低,要求一致稳定
)

@dataclass
class ThoughtNode:
    content: str           # 这个思维节点的内容
    score: float = 0.0     # 评估得分(0-10)
    depth: int = 0         # 在树中的深度
    children: List["ThoughtNode"] = field(default_factory=list)
    parent: Optional["ThoughtNode"] = None

# 生成候选思路的 Prompt
generate_prompt = ChatPromptTemplate.from_messages([
    ("system", """你是一个严谨的逻辑推理助手。
给定一个问题和当前的推理状态,生成 {n_thoughts} 个不同的下一步思路。
每个思路独立一行,用"思路N:"开头。思路要具体可执行,不要泛泛而谈。"""),
    ("human", """问题:{problem}

当前推理状态:
{current_state}

请生成 {n_thoughts} 个不同的下一步思路:"""),
])

# 评估思路质量的 Prompt
evaluate_prompt = ChatPromptTemplate.from_messages([
    ("system", """你是一个严格的推理质量评估者。
评估给定思路是否是解决问题的有效步骤。
只输出一个 0-10 的整数分数(10 分最好),不要其他内容。"""),
    ("human", """问题:{problem}

当前思路:{thought}

评分(0-10):"""),
])

def generate_thoughts(problem: str, current_state: str, n_thoughts: int = 3) -> List[str]:
    """生成多个候选思路"""
    messages = generate_prompt.format_messages(
        problem=problem,
        current_state=current_state,
        n_thoughts=n_thoughts,
    )
    response = generator_llm.invoke(messages).content
    # 解析"思路N:"格式
    thoughts = []
    for line in response.split("\n"):
        line = line.strip()
        if line and ("思路" in line or line[0].isdigit()):
            # 去掉"思路N:"前缀
            thought = line.split(":", 1)[-1].strip() if ":" in line else line
            if thought:
                thoughts.append(thought)
    return thoughts[:n_thoughts]

def evaluate_thought(problem: str, thought: str) -> float:
    """让 LLM 对一个思路打分"""
    messages = evaluate_prompt.format_messages(
        problem=problem,
        thought=thought,
    )
    response = evaluator_llm.invoke(messages).content.strip()
    try:
        score = float(re.search(r"\d+", response).group())
        return min(10.0, max(0.0, score))
    except (AttributeError, ValueError):
        return 5.0  # 解析失败时给中间分

def tot_solve(problem: str, max_depth: int = 3, n_thoughts: int = 3, beam_width: int = 2) -> str:
    """
    Tree of Thoughts 求解
    beam_width:每层保留的最优节点数(Beam Search)
    max_depth:最大探索深度
    """
    import re

    # 初始状态:空
    root = ThoughtNode(content="开始分析", depth=0)
    current_level = [root]

    for depth in range(1, max_depth + 1):
        next_level_candidates = []

        for node in current_level:
            # 从当前节点生成子思路
            current_state = f"已有推理:{node.content}" if node.content != "开始分析" else "尚未开始"
            thoughts = generate_thoughts(problem, current_state, n_thoughts)

            for thought_text in thoughts:
                score = evaluate_thought(problem, thought_text)
                child = ThoughtNode(
                    content=thought_text,
                    score=score,
                    depth=depth,
                    parent=node,
                )
                node.children.append(child)
                next_level_candidates.append(child)

        if not next_level_candidates:
            break

        # Beam Search:只保留得分最高的 beam_width 个节点继续探索
        next_level_candidates.sort(key=lambda x: x.score, reverse=True)
        current_level = next_level_candidates[:beam_width]

        print(f"第 {depth} 层,保留 {len(current_level)} 个节点:")
        for node in current_level:
            print(f"  [分数 {node.score:.1f}] {node.content[:60]}...")

    # 找到得分最高的叶节点,回溯整条推理链
    best_node = max(current_level, key=lambda x: x.score)

    # 回溯路径
    path = []
    node = best_node
    while node is not None and node.content != "开始分析":
        path.append(node.content)
        node = node.parent
    path.reverse()

    return "\n".join([f"步骤 {i+1}{step}" for i, step in enumerate(path)])


# 测试
problem = "一个公司有 100 名员工,其中 60% 会编程,40% 会设计,25% 两者都会。只会编程不会设计的有多少人?"

print(f"问题:{problem}\n")
print("ToT 推理过程:")
solution = tot_solve(problem, max_depth=2, n_thoughts=3, beam_width=2)
print(f"\n最优推理路径:\n{solution}")

CoT vs Self-Consistency vs ToT 对比

Tree of Thoughts

剪枝

问题

思路A 得8分

思路B 得4分

深化 得9分

深化 得6分

×

最优路径

Self-Consistency

问题

路径A → 答案7

路径B → 答案7

路径C → 答案8

投票 → 7

Chain of Thought

问题

步骤1

步骤2

答案

方法 推理方式 适用场景 API 调用次数 延迟
标准 CoT 单条线性推理 中等复杂度推理 1
Self-Consistency 多次独立采样投票 数学题、有唯一正确答案的推理 N(通常 5-10) 高(但可并发)
ToT 树状探索 + 剪枝 开放性问题、需要回溯的复杂规划 N×M(深度×宽度) 很高

计算代价与适用场景的权衡

Self-Consistency 和 ToT 都属于**推理时扩展(Inference-Time Scaling)**技术:通过在模型推理阶段投入更多计算资源来提升效果,而不是训练更大的模型。这与训练时扩展(更多数据、更大模型)是两个不同的维度。

理解计算代价有助于做出合理的选择:

Self-Consistency 的代价是线性的:5 次采样就是 5 倍的 API 调用成本。但各次采样是独立的,可以并发发出,延迟不会线性增加——5 个并发请求的延迟接近单次请求的延迟。在成本和延迟之间,它以成本换来了更高的准确率。

ToT 的代价是指数级的:深度 3、每层分叉 3 个、保留 2 条路径,粗估需要 3×3×2=18 次左右的 LLM 调用(生成 + 评估)。而且这些调用大多有依赖关系(下一层依赖上一层的结果),并发度有限,延迟会显著增加。

用标准 CoT:任务需要推理,但不是高风险场景,单次结果可接受,对延迟和成本敏感。

用 Self-Consistency

  • 数学计算、逻辑推理,答案有唯一正解
  • 应用错误代价高(医疗、法律、金融决策)
  • 可以并发发出多个请求,延迟不会增加太多

用 ToT

  • 问题需要"尝试-回溯"的探索,比如迷宫求解、代码调试
  • 中间步骤的评估可以大幅剪枝,避免无效探索
  • 延迟和成本不是主要约束

实践中,Self-Consistency 的性价比远高于 ToT。ToT 实现复杂,调用次数多,目前主要是研究方向,生产环境落地案例不多。对于大多数需要提升推理准确率的场景,Self-Consistency 是更务实的选择。

原则是:不要盲目用最复杂的技术。从 Zero-shot 开始,加 CoT,还不够再考虑 Self-Consistency;只有真正需要多步骤回溯探索时才考虑 ToT。

下一篇讨论角色扮演与人设设计——如何让 Agent 在不同场景下保持一致的行为风格,以及如何防止角色崩坏。

本页目录