第2314篇:Tree of Thought的工程实现——给AI的思考过程加分支
2026/4/30大约 7 分钟
第2314篇:Tree of Thought的工程实现——给AI的思考过程加分支
适读人群:AI系统工程师、算法架构师 | 阅读时长:约17分钟 | 核心价值:理解Tree of Thought的工程架构,掌握在复杂决策和创意生成场景下的实战落地方法
Chain of Thought让AI一条路走到底。但现实中很多问题,最优解法并不是一开始就能预判的,需要先走几步,发现这条路走不通,然后回头换一条路。
Chain of Thought做不到这一点,它只能直线前行。这就像走迷宫只能直走——碰壁了也不知道,或者知道了也没法回退。
我在做一个技术方案评估工具时,深刻体会到了这个局限。用户输入一个技术需求,系统要给出评估报告,需要从多个角度分析(技术可行性、成本、团队能力匹配、时间线),每个角度又有不同的假设。用CoT生成的报告,经常是"顺着第一个想法一路往下,忘了还有另一个更重要的角度"。
Tree of Thought(ToT)把这个过程变成了真正的树状探索:在每个关键节点生成多个分支,评估每个分支的价值,选择最有前途的分支继续展开,对看起来没希望的分支剪枝。
Tree of Thought的核心架构
ToT有三个核心组件:
- 思维生成器(Thought Generator):给定当前节点,生成多个可能的"下一步思路"
- 状态评估器(State Evaluator):评估每个节点有多大可能达到好的最终答案
- 搜索算法(Search Algorithm):BFS(广度优先)或 DFS + 剪枝(深度优先)
思维节点的数据结构
/**
* 思维树中的节点
*/
public class ThoughtNode {
private final String nodeId;
private final String thought; // 这个节点代表的思路/想法
private final int depth; // 在树中的深度
private final ThoughtNode parent; // 父节点
private final List<ThoughtNode> children = new ArrayList<>();
private double valueScore; // 评估分数(越高越有价值)
private NodeStatus status; // OPEN | PRUNED | COMPLETED
public enum NodeStatus {
OPEN, // 尚未探索完毕
PRUNED, // 已被剪枝(价值低)
COMPLETED // 已到达叶节点
}
/**
* 获取从根到该节点的完整思维路径
*/
public List<String> getThoughtPath() {
List<String> path = new ArrayList<>();
ThoughtNode current = this;
while (current != null) {
if (current.thought != null) {
path.add(0, current.thought);
}
current = current.parent;
}
return path;
}
/**
* 获取完整的上下文(从根节点到当前节点的所有思路)
*/
public String getContextualThought() {
return String.join("\n→ ", getThoughtPath());
}
}思维生成器
@Component
public class ThoughtGenerator {
private final ChatClient generationClient;
private static final String GENERATION_PROMPT = """
你正在探索如何回答一个复杂问题。
当前已有的思路如下(从问题出发,按顺序展开):
%s
请从当前思路出发,生成%d个不同的"下一步探索方向"。
每个方向应该从不同角度或维度深入,而不是重复已有的内容。
原始问题:%s
输出格式(JSON数组):
[
{"thought": "方向1的核心想法", "rationale": "为什么要探索这个方向"},
{"thought": "方向2的核心想法", "rationale": "为什么要探索这个方向"},
...
]
""";
public List<ThoughtDraft> generateNextThoughts(String originalQuestion,
ThoughtNode currentNode,
int numThoughts) {
String currentPath = currentNode.getContextualThought();
if (currentPath.isBlank()) {
currentPath = "(从问题出发,尚未展开)";
}
String response = generationClient.prompt()
.user(GENERATION_PROMPT.formatted(currentPath, numThoughts, originalQuestion))
.call()
.content();
return parseThoughtDrafts(response);
}
}状态评估器
@Component
public class StateEvaluator {
private final ChatClient evaluationClient;
private static final String EVALUATION_PROMPT = """
你需要评估一个思维路径对最终回答问题有多大价值。
评估维度:
1. 相关性:这条思路是否与问题的核心相关?
2. 深度:这条思路是否在深入挖掘而非流于表面?
3. 可行性:沿着这条思路,是否可能得出有实质意义的结论?
4. 覆盖新角度:是否覆盖了尚未探索的重要方面?
输出格式(JSON):
{
"valueScore": 0-10,
"shouldExplore": true/false,
"reasoning": "评估理由(一句话)"
}
评分标准:8+分继续展开,5-7分有条件展开,低于5分剪枝。
""";
/**
* 评估单个节点的价值
*/
public NodeEvaluation evaluate(String originalQuestion, ThoughtNode node) {
String response = evaluationClient.prompt()
.system(EVALUATION_PROMPT)
.user("""
原始问题:%s
完整思维路径:
%s
""".formatted(originalQuestion, node.getContextualThought()))
.call()
.content();
return parseEvaluation(response);
}
/**
* 批量评估多个节点(并行执行)
*/
public List<NodeEvaluation> evaluateBatch(String question, List<ThoughtNode> nodes) {
List<CompletableFuture<NodeEvaluation>> futures = nodes.stream()
.map(node -> CompletableFuture.supplyAsync(() -> evaluate(question, node)))
.toList();
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
return futures.stream().map(CompletableFuture::join).toList();
}
}BFS搜索算法实现
@Service
public class ToTBFSSearcher {
private final ThoughtGenerator thoughtGenerator;
private final StateEvaluator stateEvaluator;
private final ChatClient conclusionClient;
// BFS配置
private static final int BEAM_WIDTH = 3; // 每层保留的最优分支数
private static final int MAX_DEPTH = 3; // 最大探索深度
private static final int THOUGHTS_PER_NODE = 3; // 每个节点生成的思路数
private static final double PRUNE_THRESHOLD = 5.0; // 低于此分剪枝
public ToTResult search(String question) {
// 初始化根节点
ThoughtNode root = new ThoughtNode("root", null, 0, null);
// BFS队列,初始包含根节点
Queue<ThoughtNode> beam = new LinkedList<>();
beam.add(root);
List<ThoughtNode> completedNodes = new ArrayList<>();
for (int depth = 0; depth < MAX_DEPTH; depth++) {
List<ThoughtNode> nextBeam = new ArrayList<>();
// 展开当前beam中的所有节点
for (ThoughtNode currentNode : beam) {
// 生成下一步思路
List<ThoughtDraft> drafts = thoughtGenerator.generateNextThoughts(
question, currentNode, THOUGHTS_PER_NODE
);
// 创建子节点
List<ThoughtNode> children = drafts.stream()
.map(d -> new ThoughtNode(UUID.randomUUID().toString(),
d.thought(), depth + 1, currentNode))
.toList();
currentNode.addChildren(children);
// 批量评估子节点
List<NodeEvaluation> evaluations = stateEvaluator.evaluateBatch(question, children);
for (int i = 0; i < children.size(); i++) {
ThoughtNode child = children.get(i);
NodeEvaluation eval = evaluations.get(i);
child.setValueScore(eval.valueScore());
if (eval.valueScore() < PRUNE_THRESHOLD) {
child.setStatus(ThoughtNode.NodeStatus.PRUNED);
log.debug("剪枝节点: score={:.1f}, thought={}",
eval.valueScore(), child.getThought());
} else if (depth == MAX_DEPTH - 1) {
// 到达最大深度,标记为完成
child.setStatus(ThoughtNode.NodeStatus.COMPLETED);
completedNodes.add(child);
} else {
nextBeam.add(child);
}
}
}
if (nextBeam.isEmpty()) break;
// 保留评分最高的 BEAM_WIDTH 个节点
beam = nextBeam.stream()
.sorted(Comparator.comparingDouble(ThoughtNode::getValueScore).reversed())
.limit(BEAM_WIDTH)
.collect(Collectors.toCollection(LinkedList::new));
log.info("深度{}:beam中有{}个节点", depth + 1, beam.size());
}
// 如果没有到达叶节点,把beam中的节点也算作候选
if (completedNodes.isEmpty()) {
completedNodes.addAll(beam);
}
// 从所有候选叶节点中找最优,生成最终答案
ThoughtNode bestNode = completedNodes.stream()
.max(Comparator.comparingDouble(ThoughtNode::getValueScore))
.orElse(root);
String finalAnswer = generateFinalAnswer(question, bestNode);
return new ToTResult(question, root, bestNode, finalAnswer, completedNodes.size());
}
private String generateFinalAnswer(String question, ThoughtNode bestNode) {
String thoughtPath = bestNode.getContextualThought();
return conclusionClient.prompt()
.system("基于以下思维路径,给出对问题的完整、综合性回答。")
.user("问题:%s\n\n思维探索路径:\n%s\n\n请基于以上思维过程,给出最终回答。"
.formatted(question, thoughtPath))
.call()
.content();
}
}DFS + 回溯的实现
BFS适合横向广度探索,DFS适合需要深度挖掘的场景:
@Service
public class ToTDFSSearcher {
private final ThoughtGenerator thoughtGenerator;
private final StateEvaluator stateEvaluator;
private ThoughtNode bestLeaf = null;
private double bestScore = 0.0;
public ThoughtNode search(String question, ThoughtNode node, int maxDepth) {
if (node.getDepth() >= maxDepth) {
// 到达叶节点
if (node.getValueScore() > bestScore) {
bestScore = node.getValueScore();
bestLeaf = node;
}
return node;
}
// 生成子节点
List<ThoughtDraft> drafts = thoughtGenerator.generateNextThoughts(question, node, 3);
List<ThoughtNode> children = drafts.stream()
.map(d -> new ThoughtNode(UUID.randomUUID().toString(),
d.thought(), node.getDepth() + 1, node))
.toList();
node.addChildren(children);
// 评估并排序(优先探索高价值分支)
List<NodeEvaluation> evaluations = stateEvaluator.evaluateBatch(question, children);
List<ThoughtNode> sortedChildren = IntStream.range(0, children.size())
.boxed()
.sorted((i, j) -> Double.compare(
evaluations.get(j).valueScore(),
evaluations.get(i).valueScore()))
.map(children::get)
.toList();
for (int i = 0; i < sortedChildren.size(); i++) {
ThoughtNode child = sortedChildren.get(i);
NodeEvaluation eval = evaluations.get(i);
child.setValueScore(eval.valueScore());
// 剪枝:低价值分支不展开
if (eval.valueScore() < 4.0) {
child.setStatus(ThoughtNode.NodeStatus.PRUNED);
continue;
}
// 递归探索
search(question, child, maxDepth);
// 如果已找到足够好的答案,提前终止
if (bestScore >= 8.5) {
break;
}
}
return bestLeaf;
}
}适用场景分析
ToT的额外开销是显著的:一个3层深、每层3个分支的树,需要约9-27次LLM调用。所以要清楚它在哪些场景值得:
强烈推荐ToT的场景:
- 开放性创意任务(写作大纲、方案设计)
- 需要全局最优解而非局部最优的规划问题
- 答案空间大、需要系统性探索的决策问题
不推荐ToT的场景:
- 有明确答案的事实查询
- 实时性要求高的对话
- 答案路径可预知的结构化任务
我们在技术方案评估工具中用了ToT(DFS版本,深度2),整体LLM调用从1次变成了7-12次,但方案覆盖的角度明显更全面,技术经理对"是否考虑了所有关键因素"的满意度从60%提升到了88%。
