第1722篇:思维链提示(Chain-of-Thought)的变体——ToT、GoT、AoT深度解析
第1722篇:思维链提示(Chain-of-Thought)的变体——ToT、GoT、AoT深度解析
说起思维链(CoT),做过LLM应用的人基本都知道。"让我们一步步思考"(Let's think step by step)这句咒语,让很多推理任务的准确率直接翻番。但大多数人对CoT的认知停留在这一句话上。
实际上,思维链从2022年被提出到现在,已经演化出了一个庞大的家族。今天我们聊它的三个重要变体:树状思维(Tree of Thoughts,ToT)、图状思维(Graph of Thoughts,GoT)、以及算法思维(Algorithm of Thoughts,AoT)。
理解这三个,能让你在遇到复杂推理任务时,不再只会一招"让我们一步步思考"。
先回顾一下标准CoT的局限
标准CoT的结构是线性的:
问题 → 思考步骤1 → 思考步骤2 → 思考步骤3 → 答案这个结构在大多数问题上工作得很好,但有几类问题它搞不定:
需要探索多条路径的问题:比如数学竞赛题,有时候需要先试一种解法,发现走不通,退回来换另一条路。CoT的线性结构一旦走偏就没有回头路。
需要综合多个独立信息流的问题:比如复杂的因果分析,多条证据链需要交叉验证,线性思维容易丢失某条链上的关键信息。
需要对中间结论进行多次修正的问题:标准CoT生成的每一步都是最终输出,不能"反悔"。
树状思维(Tree of Thoughts,ToT)
ToT的核心思想很直观:把思考过程从一条线变成一棵树,每个节点代表一个中间思考状态,每条边代表一个推理步骤,然后用搜索算法(BFS或DFS)在这棵树上找最优路径。
ToT需要三个组件:
- 思维生成器:给定当前状态,生成k个候选下一步
- 状态评估器:对每个候选状态打分,判断是否值得继续探索
- 搜索控制器:决定搜索策略(BFS/DFS)和剪枝策略
public class TreeOfThoughtsEngine {
private final LLMClient llmClient;
private static final int BRANCHING_FACTOR = 3; // 每个节点生成3个候选
private static final double PRUNING_THRESHOLD = 0.4; // 评分低于0.4的节点剪枝
public ThoughtNode solve(String problem, SearchStrategy strategy) {
ThoughtNode root = new ThoughtNode(problem, null, 1.0);
if (strategy == SearchStrategy.BFS) {
return solveBFS(root);
} else {
return solveDFS(root, 0, 5); // DFS最大深度5
}
}
private ThoughtNode solveBFS(ThoughtNode root) {
Queue<ThoughtNode> queue = new LinkedList<>();
queue.offer(root);
ThoughtNode bestSolution = null;
while (!queue.isEmpty()) {
ThoughtNode current = queue.poll();
// 判断是否已经得到解
if (isSolution(current)) {
if (bestSolution == null || current.getScore() > bestSolution.getScore()) {
bestSolution = current;
}
continue;
}
// 生成候选下一步
List<String> candidates = generateCandidates(current);
// 评估每个候选
for (String candidate : candidates) {
double score = evaluateThought(current, candidate);
if (score >= PRUNING_THRESHOLD) {
ThoughtNode childNode = new ThoughtNode(candidate, current, score);
current.addChild(childNode);
queue.offer(childNode);
}
}
}
return bestSolution;
}
private List<String> generateCandidates(ThoughtNode currentNode) {
String generationPrompt = """
当前问题:%s
已有的思考过程:
%s
请生成%d个不同的下一步推理方向。每个方向应该有实质性差异,不能只是换个说法。
以JSON数组格式输出,每个元素是一个推理步骤描述。
""".formatted(
extractOriginalProblem(currentNode),
buildThoughtChain(currentNode),
BRANCHING_FACTOR
);
String response = llmClient.complete(generationPrompt);
return parseJsonArray(response);
}
private double evaluateThought(ThoughtNode parent, String thoughtContent) {
String evaluationPrompt = """
原始问题:%s
当前思考路径:
%s
候选下一步:%s
请评估这个下一步的质量(0-1分):
- 1.0:非常有价值,极大推进问题解决
- 0.7:有帮助,能推进问题
- 0.4:勉强有用,但方向不太对
- 0.1-0.3:偏离主题或有明显错误
- 0:完全错误或无关
只输出一个小数,不要任何解释。
""".formatted(
extractOriginalProblem(parent),
buildThoughtChain(parent),
thoughtContent
);
String scoreStr = llmClient.complete(evaluationPrompt).trim();
return Double.parseDouble(scoreStr);
}
}ToT适合什么场景
实际测试下来,ToT在以下场景表现明显优于标准CoT:
- 数独、24点这类搜索型谜题:需要尝试不同路径,失败了要回溯
- 创意写作中的情节规划:需要探索多种故事走向
- 代码调试:需要假设多种出错原因,逐一验证
但ToT有个很实际的问题:成本高。生成BRANCHING_FACTOR个候选,每个都要评估,实际消耗的Token可能是标准CoT的5-10倍。在生产环境里,你需要认真权衡。
图状思维(Graph of Thoughts,GoT)
ToT是树形结构,每个节点只有一个父节点。GoT把限制去掉了——它允许多个节点合并成一个新节点,形成真正的图结构。
这个区别听起来很学术,但实际上解决了一个很具体的问题:多路信息合并。
比如你在分析一个业务问题,需要同时考虑用户体验、技术可行性、商业价值三个维度。ToT里这三条分析链是独立的,没法在中途把它们的结论整合起来形成新的推理。GoT可以。
public class GraphOfThoughtsEngine {
private final LLMClient llmClient;
// GoT的核心数据结构
@Data
public static class ThoughtGraph {
private Map<String, GraphNode> nodes = new HashMap<>();
private List<GraphEdge> edges = new ArrayList<>();
public void addNode(GraphNode node) {
nodes.put(node.getId(), node);
}
public void addEdge(String fromId, String toId, String operation) {
edges.add(new GraphEdge(fromId, toId, operation));
}
// 获取某节点的所有前驱节点
public List<GraphNode> getPredecessors(String nodeId) {
return edges.stream()
.filter(e -> e.getToId().equals(nodeId))
.map(e -> nodes.get(e.getFromId()))
.collect(Collectors.toList());
}
}
// GoT的三种操作
public enum GraphOperation {
GENERATE, // 从一个节点生成新节点(类似ToT的分支)
AGGREGATE, // 多个节点合并成一个(GoT的独特能力)
REFINE // 对一个节点进行细化改进
}
public String solveWithGoT(String problem) {
ThoughtGraph graph = new ThoughtGraph();
// 初始节点
GraphNode rootNode = GraphNode.of("root", problem);
graph.addNode(rootNode);
// 阶段1:并行展开多个分析维度
List<String> dimensions = identifyAnalysisDimensions(problem);
List<GraphNode> dimensionNodes = new ArrayList<>();
for (String dimension : dimensions) {
String analysis = analyzeFromDimension(problem, dimension);
GraphNode dimNode = GraphNode.of(dimension, analysis);
graph.addNode(dimNode);
graph.addEdge("root", dimension, GraphOperation.GENERATE.name());
dimensionNodes.add(dimNode);
}
// 阶段2:聚合所有维度的结论
String aggregatedContent = aggregateNodes(dimensionNodes, problem);
GraphNode aggregateNode = GraphNode.of("aggregate", aggregatedContent);
graph.addNode(aggregateNode);
for (GraphNode dimNode : dimensionNodes) {
graph.addEdge(dimNode.getId(), "aggregate", GraphOperation.AGGREGATE.name());
}
// 阶段3:在聚合结论上进行精化
String refinedContent = refineNode(aggregateNode, problem);
GraphNode finalNode = GraphNode.of("final", refinedContent);
graph.addNode(finalNode);
graph.addEdge("aggregate", "final", GraphOperation.REFINE.name());
return finalNode.getContent();
}
private String aggregateNodes(List<GraphNode> nodes, String originalProblem) {
StringBuilder nodeContents = new StringBuilder();
for (GraphNode node : nodes) {
nodeContents.append("视角【").append(node.getId()).append("】:\n");
nodeContents.append(node.getContent()).append("\n\n");
}
String aggregatePrompt = """
原始问题:%s
以下是从不同维度得出的分析结论:
%s
请将这些不同维度的分析综合起来,形成一个整合性的推理结论。
注意:
1. 识别不同视角之间的一致性和矛盾点
2. 对矛盾点进行调和或说明取舍依据
3. 综合结论要比单一视角更全面深刻
""".formatted(originalProblem, nodeContents.toString());
return llmClient.complete(aggregatePrompt);
}
}算法思维(Algorithm of Thoughts,AoT)
AoT是我个人觉得最有工程实用价值的变体。它的出发点是:ToT和GoT虽然推理质量高,但Token消耗太大。能不能把搜索算法"内化"到单次LLM调用里?
AoT的答案是:让模型在一次生成过程中,模拟执行某种搜索算法。
听起来玄乎,看个例子就懂了。
标准CoT:
问题:用25分和10分硬币凑出95分钱,最少几枚?
思考:先试25分,95/25=3余20,再用两枚10分,共5枚。
答案:5枚AoT(内化了贪心搜索算法):
问题:用25分和10分硬币凑出95分钱,最少几枚?
执行贪心搜索:
[初始状态] 剩余=95,已用=0枚
[步骤1] 尝试25分硬币:95>=25,使用1枚。剩余=70,已用=1枚
[步骤2] 尝试25分硬币:70>=25,使用1枚。剩余=45,已用=2枚
[步骤3] 尝试25分硬币:45>=25,使用1枚。剩余=20,已用=3枚
[步骤4] 尝试25分硬币:20<25,切换到10分
[步骤5] 尝试10分硬币:20>=10,使用1枚。剩余=10,已用=4枚
[步骤6] 尝试10分硬币:10>=10,使用1枚。剩余=0,已用=5枚
[完成] 总计5枚
答案:5枚这个格式告诉模型用特定的算法框架来组织思考,输出是单次生成的,不需要多轮交互。
public class AlgorithmOfThoughtsPromptBuilder {
/**
* 根据问题类型选择内嵌的算法模板
*/
public String buildAoTPrompt(String problem, AlgorithmType algorithmType) {
String algorithmTemplate = switch (algorithmType) {
case GREEDY -> buildGreedyTemplate();
case DYNAMIC_PROGRAMMING -> buildDPTemplate();
case BACKTRACKING -> buildBacktrackingTemplate();
case DIVIDE_AND_CONQUER -> buildDivideAndConquerTemplate();
};
return """
%s
请使用上述算法框架,分步解决以下问题:
%s
""".formatted(algorithmTemplate, problem);
}
private String buildBacktrackingTemplate() {
return """
使用回溯搜索框架分析此问题。格式如下:
[初始化] 设定初始状态和约束条件
[选择] 从候选列表中选择一个选项
[验证] 检查当前选择是否满足约束
[继续/回溯] 如果有效则继续深入;如果无效则标记为"回溯"并尝试下一个选项
[成功/失败] 当找到解时输出[成功];当所有选项都已尝试则输出[失败]
在整个过程中显示状态变化,使推理过程完全透明。
""";
}
private String buildDPTemplate() {
return """
使用动态规划框架分析此问题。格式如下:
[问题分解] 识别子问题结构
[状态定义] 定义dp[i]代表什么
[转移方程] 写出状态转移关系
[初始条件] 设定边界值
[计算过程] 按顺序填充dp表
[最优解] 从dp表中提取答案
""";
}
}AoT最大的价值在于:它把结构化推理的成本从O(branches × depth)降低到O(1)次LLM调用。对于Token成本敏感的场景,这个差距是决定性的。
三种方法的横向对比
我做了一个简单的对比,基于20个不同类型的推理题:
实际选择建议:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 简单问答、文本处理 | 标准CoT | 够用,成本低 |
| 需要探索多种可能 | ToT+DFS | 有回溯能力 |
| 多维度综合分析 | GoT | 信息融合能力强 |
| 有明确算法逻辑 | AoT | 单次调用,成本低 |
| 生产环境高频调用 | AoT或标准CoT | Token成本控制 |
一个容易被忽视的点
这三种变体在实际工程中,有一个共同的难点:如何判断搜索终止。
ToT/GoT需要一个评估器来决定哪些节点值得继续探索,评估器的质量直接决定了整个系统的效果。我见过不少实现,在评估这步偷懒,随便让模型输出一个"好/不好",导致搜索完全不靠谱。
一个好的评估提示词,至少要做到:
- 给出评分的具体标准(不能只说"评估这个思路的质量")
- 要求模型先分析优缺点,再给分数(强制推理过程,减少随机性)
- 对分数区间有明确的语义定义(0.8和0.7代表什么不同?)
// 规范的评估提示词模板
String rigourousEvalPrompt = """
评估以下思考步骤对于解决原始问题的价值。
原始问题:%s
当前思考路径:%s
待评估步骤:%s
评估维度(请先分析,再打分):
1. 相关性(0-1):这个步骤是否朝着解决问题的方向前进?
2. 准确性(0-1):步骤中的推理是否正确?有没有明显的逻辑错误?
3. 进展性(0-1):这个步骤比之前的状态有实质性推进吗?
分析完三个维度后,给出综合评分(三个维度的加权平均,权重分别为0.4、0.4、0.2)。
输出格式:
相关性分析:[你的分析]
相关性评分:[0-1的小数]
准确性分析:[你的分析]
准确性评分:[0-1的小数]
进展性分析:[你的分析]
进展性评分:[0-1的小数]
综合评分:[计算结果]
""";小结
CoT从一条线,进化到树(ToT),再进化到图(GoT),再内化算法(AoT)。这条演化路径的主线是:更强的探索能力 vs 更低的计算成本。
没有哪种方案是银弹,关键是根据任务类型和成本约束做选择。作为Java工程师,我们的优势在于能把这些策略封装成工程化的框架,不用每次都从头实现。
下一篇我们聊对抗性提示,那个方向同样值得深入。
