Menu

思维树提示(Tree of Thought):原理与示例

思维树提示让模型探索几个不完整的解法,逐一评判,只保留有希望的分支。本文介绍原始方法的原理、一个可以直接试用的单提示词版本,以及这个版本的不足之处。

下面的每条提示词都可以编辑:改好后,在 ChatGPT、Claude 或其他 AI 应用中打开。

思维树提示让语言模型像你在纸上解题那样处理问题:写下几个可能的下一步,判断哪些看起来有希望,沿着它们继续,某个分支走不通时就放弃。这个想法出自 Yao 等人 2023 年的论文《Tree of Thoughts: Deliberate Problem Solving with Large Language Models》。它把只沿一条推理路线走的思维链提示扩展成了在多条路线之间的搜索。

本文介绍原始方法的原理,给你一个可以在 ChatGPT、Claude 或 Gemini 中试用的单提示词版本,并在一个提示词不够用时,展示如何用代码实现这个循环。

思维树的原理

论文把这个方法拆成四个决定。

  1. 什么算一个想法。 一个想法是一个中间步骤,小到模型能把它写好,大到足以被评判。在数学谜题中,它是一个算式;在写作任务中,它是一份简短的计划。
  2. 如何生成想法。 模型从当前不完整的解法出发,提出几个候选的下一步,可以独立采样,也可以在一个回复中列出来。
  3. 如何评估想法。 让模型给每个不完整的解法打分。论文用了两种方式:单独给每个状态打分(例如“肯定可以”“有可能”“不可能”),或者把几个状态一起给模型看,让它投票选出最好的。
  4. 如何搜索。 程序在每一层保留最好的几个状态(广度优先搜索),或者沿着一个分支深入下去,当评估认为没有希望时再退回来(深度优先搜索)。

以论文中的 24 点游戏为例:用 4、9、10、13 这四个数,每个数用一次,通过四则运算得到 24。思维链会认定它写出的第一个算式,然后只能将错就错。思维树则可能把 13 - 9 = 4、10 - 4 = 6 和 4 + 9 = 13 都作为第一步试一试,问模型哪些剩下的数还能凑出 24,舍弃死路(10、13、13 凑不出 24),最终得到 (10 - 4) * (13 - 9) = 24。

论文的发现

作者挑选了三个即使用思维链 GPT-4 也觉得困难的任务,因为每个任务都需要计划或搜索:24 点游戏、一个结尾句子固定的创意写作任务,以及 5x5 的迷你填字游戏。使用 GPT-4 时,树搜索解出的 24 点题目,远多于思维链提示,也远多于采样多条推理链再取多数答案的方法,在另外两个任务上也表现更好。

代价是调用次数。每个候选步骤和每次评估都是一次单独的请求,所以思维链只需一次调用,而解出一道题可能要调用模型几十次。对难题来说,这个取舍是值得的;对简单的问题则毫无意义。

单个提示词实现的思维树

你可以在一条消息里近似实现这个想法:要求模型给出几种不同的方法,根据你提供的事实逐一下结论,只展开留下来的那一种。下面的标签页用这两种方式发送同一个问题。

我们的网页应用对欧洲用户来说很快,但澳大利亚用户加载要 12 秒左右。服务器在法兰克福。页面加载时,浏览器会一个接一个地发出 40 个小的 API 请求。原因是什么?我们该怎么办?
Try it
Example replyReplies vary between models and runs.

主要原因是澳大利亚与你在法兰克福的服务器之间的物理距离。每个请求都要往返欧洲一趟,每次页面加载有 40 个请求,延迟就累积起来了。

标准的解决办法是使用 CDN(内容分发网络)。CDN 会在世界各地的服务器上保存你网站的副本,让澳大利亚用户从附近的节点下载,而不是从法兰克福下载。大多数提供商都允许你在现有网站前面加一层 CDN,不需要改代码。

你还可以压缩图片、开启浏览器缓存,进一步缩短加载时间。

直接回答并不离谱,它甚至算上了 40 个请求,但它抓住了第一个说得通的解释(“距离”)就一路说下去。分支式的回答则用提示词中的细节检验了每种解释,因此注意到“一个接一个”比“澳大利亚”更关键,也发现 CDN 根本不会影响那些 API 调用。

一个可复用的模板

同样的结构也适用于设计选择和制定计划。填入你的问题和你在意的标准;回复展示的是填入默认值时的结果。

分支、评判、选择
Fill in
问题:我们是一个三人团队,需要选择如何部署一个小型 Node.js API。 标准:每月费用、搭建时间、日常维护 提出 3 种真正不同的方法。对每种方法,用简短的一行按每条标准打分,并给出结论:保留或舍弃。然后只把最好的方法展开成五个具体的初始步骤。如果有两种方法不相上下,说明需要什么信息才能在它们之间做出决定。
Try it
Example replyReplies vary between models and runs.

平台即服务(托管式应用平台) 费用:中等,随流量增加。搭建:从 Git 仓库部署,不到一小时。维护:少,由平台负责系统补丁。保留。

自己的虚拟服务器 费用:最低。搭建:服务器、TLS 和进程管理器要花一天。维护:多,更新和重启都得自己来。对三人团队来说舍弃。

Serverless 函数 费用:小规模时非常低。搭建:中等,现有的服务器应用可能需要重新组织。维护:少,但冷启动会增加延迟。作为备选保留。

最佳方案:平台即服务。 初始步骤:

  1. 添加一个启动脚本,并从环境变量读取端口。
  2. 把密钥移到平台的环境变量设置中。
  3. 连接代码仓库,部署 main 分支。
  4. 添加一个健康检查接口。
  5. 设置费用提醒。

单个提示词的不足之处

单提示词版本保留了这个方法的术语,却丢掉了它的大部分机制。

  • 没有真正的回溯。 模型在一遍之内写出所有分支和结论。如果选中分支的第 3 步走不通,没有任何东西会把它送回第 1 步。
  • 评判者就是作者。 提出想法的同一个回复也在给它打分,所以它往往偏向自己心中早已认定的那个分支。在论文中,评估是在候选项产生之后单独进行的一次调用。
  • 分支并不独立。 在一个回复中列出的想法会相互影响,最后常常变成同一个主题的几种变体。像模板那样要求“真正不同”的方法能缓解这一点,但无法消除。
  • 推理模型本来就会分支。 会先思考再回答的模型已经在内部尝试并舍弃各种方法。对它们来说,这个提示词在准确率上的提升有限;它剩下的价值在于,你能看到被否决的选项,并且可以不同意它的推理。

用代码运行真正的树搜索

完整的方法是你程序中的一个循环:生成候选步骤,用单独的调用给每个不完整的解法打分,保留最好的几个,然后重复。下面是一个使用 OpenAI Python SDK 的最简广度优先版本;同样的结构适用于任何提供商。

from openai import OpenAI

client = OpenAI()
MODEL = "your-model-id"  # e.g. from your provider's model list

def ask(prompt, temperature=0.7):
    response = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": prompt}],
        temperature=temperature,
    )
    return response.choices[0].message.content.strip()

def propose(problem, path, k=3):
    steps = "\n".join(path) or "(none yet)"
    prompt = f"Problem: {problem}\nSteps so far:\n{steps}\nPropose the next step only."
    return [ask(prompt) for _ in range(k)]

def score(problem, path):
    steps = "\n".join(path)
    prompt = (f"Problem: {problem}\nPartial solution:\n{steps}\n"
              "How likely is this to lead to a correct solution? Reply with a number from 1 to 10 only.")
    try:
        return float(ask(prompt, temperature=0))
    except ValueError:
        return 0.0

def tree_of_thought(problem, depth=3, keep=2):
    frontier = [[]]
    for _ in range(depth):
        candidates = [path + [step] for path in frontier for step in propose(problem, path)]
        candidates.sort(key=lambda p: score(problem, p), reverse=True)
        frontier = candidates[:keep]
    return frontier[0]

当 depth=3、keep=2,每个状态提出三个候选时,一次运行大约要调用三十次。对很多任务来说,自洽性提示(多个完整答案,多数投票)或者用提示词链串起固定的步骤,就能以更少的调用获得大部分好处。只有当任务需要搜索时,才值得用树:可能的第一步很多,而且有办法及早识别死路。

常见问题

思维树提示是什么?

思维树提示是一种解决问题的方法:模型提出几个可能的下一步,评估每一步有多大希望,只沿着最好的分支继续,某个分支走不通时就退回去。它出自 Yao 等人 2023 年的论文《Tree of Thoughts: Deliberate Problem Solving with Large Language Models》。在论文中,分支和打分是由一个多次调用模型的程序来完成的。

思维树和思维链有什么区别?

思维链从头到尾只沿着一条推理路线走,所以早期的错误会一直带到答案里。思维树同时保留几个不完整的解法,评估它们,并舍弃弱的那些,所以能从糟糕的第一步中恢复过来。代价是需要多得多的模型调用。

能在 ChatGPT 或 Claude 里使用思维树吗?

可以用一种近似的方法:写一个提示词,要求模型列出几种方法,按你的标准逐一评判,舍弃弱的,再展开最好的那一种。它在任何聊天应用中都能用。但这不是完整的方法,因为一切都发生在一个回复里,而且模型是在写出这些想法的同一遍里给它们打分的。

对推理模型来说,思维树提示还有用吗?

没有以前那么有用了。会先思考再回答的模型已经在内部尝试并舍弃各种方法,所以再让它们分支,带来的提升有限。不过,当你想看到各个选项以及它们被否决的理由,以便自己检查判断时,单提示词版本仍然有用。

什么时候应该使用思维树提示?

用在有几种看似可行的方法、而第一个想法又常常是错的问题上:做计划、设计选择、根据症状诊断问题,以及需要搜索的谜题。对于只有一条明显路线的问题,普通的思维链更省钱,效果也一样好。

Coddy 编程语言插图

和 Coddy 一起学编程

开始