通过 Game24 游戏,深入理解 Tree of Thoughts 如何让大语言模型进行多步推理和智能搜索
论文:Tree of Thoughts: Deliberate Problem Solving with Large Language Models
想象一下,当你面对一个复杂的数学问题时,你会怎么做?比如给你四个数字 1 1 4 6,要求用基本运算(+、-、*、/)得到 24。
大多数人不会直接给出答案,而是会:
1+1=2、4*6=24、6-1=5 等)1+1=2,因为得到 2 后更容易凑成 24)这就是 Tree of Thoughts (ToT) 的核心思想:让大语言模型像人类一样,通过生成、评估、选择三个步骤,逐步构建解决方案树,而不是一次性生成答案。
Tree of Thoughts 是 Princeton 大学在 2023 年提出的一种新的提示方法,它解决了传统方法(如 Chain of Thought, CoT)的局限性。
标准提示(Standard Prompting):
思维链提示(Chain of Thought, CoT):
ToT 通过构建解决方案树,让模型能够:
Game24 是一个经典的数学游戏:给定 4 个数字,使用基本运算得到 24。例如:
输入:1 2 3 4
输出:
1 + 2 = 3 (left: 3 3 4)
3 + 3 = 6 (left: 4 6)
6 * 4 = 24 (left: 24)
Answer: (1 + 2 + 3) * 4 = 24
这个任务非常适合展示 ToT,因为:
Tree of Thoughts 的核心是一个迭代的三步循环:生成 → 评估 → 选择。让我们通过一个具体的例子来理解:

在每一步,模型需要生成多个可能的下一步操作。有两种不同的生成策略:
Propose 方法:一次 API 调用,模型输出多个候选
n=1)split('\n') 将多行输出分割成多个候选示例:
提示词: "Input: 1 1 4 6\nPossible next steps:"
模型输出(一次响应):
1 + 1 = 2 (left: 2 4 6)
1 * 1 = 1 (left: 1 4 6)
4 * 6 = 24 (left: 1 1 24)
6 - 1 = 5 (left: 1 4 5)
4 + 6 = 10 (left: 1 1 10)
...
结果: 分割成 5+ 个候选
Sample 方法:多次 API 调用,每次生成一个候选
n_generate_sample 次 API(例如 n=10 就调用 10 次)示例:
提示词: "Use numbers and basic arithmetic operations to obtain 24.\nInput: 1 1 4 6"
API 调用 1: "1 + 1 = 2 (left: 2 4 6)\n4 * 6 = 24 (left: 2 24)\n..."
API 调用 2: "4 * 6 = 24 (left: 1 1 24)\n24 / 1 = 24 (left: 1 24)\n..."
API 调用 3: "6 - 1 = 5 (left: 1 4 5)\n5 * 4 = 20 (left: 1 20)\n..."
...
结果: 10 次调用得到 10 个候选
对每个生成的候选,评估它到达正确答案的可能性。
Value 方法:单独评估每个候选的价值
模型会输出:
sure(20 分):非常有可能达到 24likely(1 分):有可能达到 24impossible(0.001 分):不可能达到 24例如:
候选: 1 + 1 = 2 (left: 2 4 6)
评估: sure # 因为 2, 4, 6 很容易得到 24(4*6=24,然后 24*1=24)
价值分数: 20.0
候选: 1 * 1 = 1 (left: 1 4 6)
评估: likely # 可能,但不如上面的好
价值分数: 1.0
Vote 方法:将所有候选一起展示,让模型投票选择最好的
根据评估分数,选择最有希望的候选进入下一步。
下面的流程图展示了每一步内部的详细流程:

Greedy 方法:贪心选择,直接选择价值分数最高的前 N 个
n_select_sample 个# 代码实现(来自 bfs_method.py)
if args.method_select == 'greedy':
# 按价值分数降序排序,选择前 n_select_sample 个
select_ids = sorted(ids, key=lambda x: values[x], reverse=True)[:args.n_select_sample]
# 示例:假设有 10 个候选,价值分数为:
values = [20.0, 1.0, 0.001, 20.0, 1.0, 0.001, 20.0, 1.0, 0.001, 0.001]
# 选择前 5 个(n_select_sample=5)
select_ids = [0, 3, 6, 1, 4] # 选择价值最高的 5 个(索引 0, 3, 6, 1, 4)
Sample 方法:按价值分数作为概率进行采样
# 代码实现(来自 bfs_method.py)
if args.method_select == 'sample':
# 将价值分数归一化为概率分布
ps = np.array(values) / sum(values)
# 按概率采样 n_select_sample 个候选
select_ids = np.random.choice(ids, size=args.n_select_sample, p=ps).tolist()
# 示例:假设有 3 个候选,价值分数为:
values = [20.0, 1.0, 0.001]
# 归一化为概率:[20.0/21.001, 1.0/21.001, 0.001/21.001] ≈ [0.952, 0.048, 0.000]
# 按这个概率分布采样,价值高的候选被选中的概率更大
让我们通过一个具体的例子,详细展示 ToT 如何逐步解决 Game24 问题:用 1 1 4 6 得到 24。
1 1 4 6下面的流程图展示了 ToT 如何逐步解决这个问题:

让我们逐步看看每个阶段发生了什么:
当前状态:1 1 4 6(4 个数字)
1.1 生成阶段(Generation)
使用 propose 方法,模型被要求列出所有可能的下一步操作。模型一次性生成了 52 个候选,包括:
- 1 + 1 = 2 (left: 2 4 6)
- 1 * 1 = 1 (left: 1 4 6)
- 4 * 6 = 24 (left: 1 1 24)
- 6 - 1 = 5 (left: 1 4 5)
- 4 + 6 = 10 (left: 1 1 10)
- 1 - 1 = 0 (left: 0 4 6)
- 6 / 1 = 6 (left: 1 4 6)
... (共 52 个候选)
1.2 评估阶段(Evaluation)
使用 value 方法,对每个候选进行评估。模型会判断每个候选到达 24 的可能性:
sure(20 分):非常有可能达到 24likely(1 分):有可能达到 24impossible(0.001 分):不可能达到 24评估结果示例:
候选: 1 + 1 = 2 (left: 2 4 6)
评估: sure (20.0) # 因为 2, 4, 6 很容易得到 24(4*6=24,然后 24*1=24)
候选: 4 * 6 = 24 (left: 1 1 24)
评估: sure (20.0) # 已经得到 24,只需要用 1 和 1 保持 24
候选: 6 - 1 = 5 (left: 1 4 5)
评估: likely (1.0) # 可能,但不如上面的好
候选: 1 * 1 = 1 (left: 1 4 6)
评估: likely (1.0) # 可能,但不如上面的好
候选: 4 + 6 = 10 (left: 1 1 10)
评估: impossible (0.001) # 很难从 1, 1, 10 得到 24
1.3 选择阶段(Selection)
使用 greedy 方法,选择价值分数最高的前 5 个候选:
✓ 1 + 1 = 2 (left: 2 4 6) [价值: 20.0]
✓ 4 * 6 = 24 (left: 1 1 24) [价值: 20.0]
✓ 6 - 1 = 5 (left: 1 4 5) [价值: 1.0]
✓ 1 * 1 = 1 (left: 1 4 6) [价值: 1.0]
✓ 4 + 6 = 10 (left: 1 1 10) [价值: 0.001]
这 5 个候选将进入下一步。
现在有 5 个候选,需要对每个候选继续生成下一步。让我们看两个最有希望的路径:
路径 A:从 1 + 1 = 2 (left: 2 4 6) 继续
2.1 生成阶段:从状态 2 4 6 生成可能的操作
- 2 * 4 = 8 (left: 8 6)
- 4 * 6 = 24 (left: 2 24)
- 2 + 4 = 6 (left: 6 6)
- 2 + 6 = 8 (left: 4 8)
...
2.2 评估阶段:
候选: 4 * 6 = 24 (left: 2 24)
评估: sure (20.0) # 已经得到 24,只需要用 2 保持 24
候选: 2 * 4 = 8 (left: 8 6)
评估: likely (1.0) # 可能,但不如上面的好
2.3 选择阶段:选择 4 * 6 = 24 (left: 2 24) [价值: 20.0]
路径 B:从 4 * 6 = 24 (left: 1 1 24) 继续
2.1 生成阶段:从状态 1 1 24 生成可能的操作
- 24 / 1 = 24 (left: 1 24)
- 24 * 1 = 24 (left: 1 24)
- 1 + 1 = 2 (left: 2 24)
- 1 * 1 = 1 (left: 1 24)
...
2.2 评估阶段:
候选: 24 / 1 = 24 (left: 1 24)
评估: sure (20.0) # 已经得到 24,只需要用最后一个 1 保持 24
候选: 1 + 1 = 2 (left: 2 24)
评估: likely (1.0) # 可能,但不如上面的好
2.3 选择阶段:选择 24 / 1 = 24 (left: 1 24) [价值: 20.0]
路径 A:从 2 24 继续
3.1 生成阶段:从状态 2 24 生成可能的操作
- 24 / 2 = 12 (left: 12)
- 2 * 24 = 48 (left: 48)
- 24 - 2 = 22 (left: 22)
...
3.2 评估阶段:这些操作都无法得到 24,价值分数都很低(0.001)
路径 B:从 1 24 继续 ✅
3.1 生成阶段:从状态 1 24 生成可能的操作
- 24 * 1 = 24 (left: 24)
- 24 / 1 = 24 (left: 24)
...
3.2 评估阶段:
候选: 24 * 1 = 24 (left: 24)
评估: sure (20.0) # 完美!已经得到 24,且使用了所有数字
3.3 选择阶段:选择 24 * 1 = 24 (left: 24) ✅
当前状态:24(只剩下一个数字,且等于 24)
4.1 生成阶段:模型需要生成完整的答案表达式
4.2 最终答案:
Steps:
1 + 1 = 2 (left: 2 4 6)
4 * 6 = 24 (left: 2 24)
24 / 2 = 12 (left: 12) # 注意:这个路径实际上没有成功
但实际上,从路径 B 得到的正确答案是:
Steps:
4 * 6 = 24 (left: 1 1 24)
24 / 1 = 24 (left: 1 24)
24 * 1 = 24 (left: 24)
Answer: (6 * 4) * 1 * 1 = 24
或者更简洁的答案:
Answer: (6 * 4) / 1 * 1 = 24
下面的树状图展示了 ToT 从初始状态到最终答案的完整搜索过程,每个节点表示当前剩余的数字:
1 1 4 6
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
1 1 24 2 4 6
(4*6=24) (1+1=2)
/ \ / \
/ \ / \
/ \ / \
/ \ / \
1 24 2 24 2 24 8 6
(24/1) (1+1=2) (4*6=24) (2*4=8)
| | | |
| └──→ ❌ └──→ ❌ └──→ ❌
|
└──→ 24 ✅
(24*1=24)
成功路径:
初始: 1 1 4 6
↓
步骤1: 4 * 6 = 24 (left: 1 1 24) [价值: 20.0] ✓
↓
步骤2: 24 / 1 = 24 (left: 1 24) [价值: 20.0] ✓
↓
步骤3: 24 * 1 = 24 (left: 24) [价值: 20.0] ✓
↓
最终答案: (6 * 4) / 1 * 1 = 24 ✅
让我们看看 ToT 的核心实现:
def solve(args, task, idx, to_print=True):
"""使用树搜索(Tree of Thoughts)方法解决问题"""
x = task.get_input(idx) # 获取输入,如 "1 1 4 6"
ys = [''] # 初始候选列表(空字符串表示还未开始)
for step in range(task.steps): # game24 需要 4 步
# ========== 第一步:生成(Generation)==========
# 对当前每个候选,生成新的候选解决方案
if args.method_generate == 'propose':
# propose 方法:生成下一步可能的操作(多个候选)
new_ys = [get_proposals(task, x, y) for y in ys]
elif args.method_generate == 'sample':
# sample 方法:直接生成完整或部分解决方案
new_ys = [get_samples(task, x, y, ...) for y in ys]
# 将嵌套列表展平(因为每个 y 可能生成多个 new_ys)
new_ys = list(itertools.chain(*new_ys))
# ========== 第二步:评估(Evaluation)==========
# 对生成的所有候选进行评估,得到价值分数
if args.method_evaluate == 'value':
# 价值方法:单独评估每个候选的价值
values = get_values(task, x, new_ys, ...)
elif args.method_evaluate == 'vote':
# 投票方法:将所有候选一起展示,让模型投票
values = get_votes(task, x, new_ys, ...)
# ========== 第三步:选择(Selection)==========
# 根据评估分数选择最有希望的候选
if args.method_select == 'greedy':
# 贪心方法:直接选择价值分数最高的前 N 个
select_ids = sorted(range(len(new_ys)),
key=lambda i: values[i],
reverse=True)[:args.n_select_sample]
elif args.method_select == 'sample':
# 采样方法:按价值分数作为概率进行采样
ps = np.array(values) / sum(values)
select_ids = np.random.choice(range(len(new_ys)),
size=args.n_select_sample,
p=ps)
select_new_ys = [new_ys[i] for i in select_ids]
ys = select_new_ys # 更新候选列表,进入下一步
return ys # 返回最终候选
def get_proposals(task, x, y):
"""使用 propose 方法生成下一步的候选操作"""
# 生成提议提示词,要求模型生成可能的下一步操作
propose_prompt = task.propose_prompt_wrap(x, y)
# 调用 GPT 生成提议
raw_output = gpt(propose_prompt, n=1, stop=None)[0]
# 输出示例:
# "1 + 1 = 2 (left: 2 4 6)\n1 * 1 = 1 (left: 1 4 6)\n4 * 6 = 24 (left: 1 1 24)\n..."
# 解析输出,每个提议作为一行
proposals = raw_output.split('\n')
# 将每个提议追加到当前路径后面
return [y + prop + '\n' for prop in proposals if prop.strip()]
def get_value(task, x, y, n_evaluate_sample, cache_value=True):
"""评估单个候选的价值分数"""
# 生成评估提示词
# 例如:评估 "1 + 1 = 2 (left: 2 4 6)" 这个状态
value_prompt = task.value_prompt_wrap(x, y)
# 调用 GPT 进行评估(生成 n_evaluate_sample 个评估结果)
value_outputs = gpt(value_prompt, n=n_evaluate_sample, stop=None)
# 输出示例:
# ["sure", "sure", "likely"] # 3 次评估中有 2 次是 "sure",1 次是 "likely"
# 将文本输出转换为数值分数
# "sure" → 20, "likely" → 1, "impossible" → 0.001
value = task.value_outputs_unwrap(x, y, value_outputs)
# 结果:2 * 20 + 1 * 1 = 41.0
return value
评估中间步骤时,模型会看到:
Evaluate if given numbers can reach 24 (sure/likely/impossible)
2 4 6
2 * 4 = 8
8 * 6 = 48
(6 - 2) * 4 = 16
I cannot obtain 24 now, but numbers are within a reasonable range
likely
评估最后一步时,模型会看到:
Use numbers and basic arithmetic operations (+ - * /) to obtain 24.
Given an input and an answer, give a judgement (sure/impossible)
Input: 1 1 4 6
Answer: (1 + 1) * (6 * 4 / 2) = 24
Judge: sure
传统方法只探索一条路径,ToT 同时探索多条路径,大大提高了找到正确答案的概率。
通过让模型评估每个候选的前景,ToT 能够优先探索最有希望的路径,而不是盲目搜索。
如果一条路径走不通,ToT 可以回到上一步,选择其他候选,这是传统方法无法做到的。
虽然 ToT 在解决复杂问题方面表现出色,但它也有一些明显的缺点:
Tree of Thoughts 通过模拟人类的思考过程——生成多个可能、评估前景、选择最优——让大语言模型能够更好地解决复杂问题。
✅ 适合的场景:
❌ 不适用场景: