UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

102 lines • 4.74 kB
"use strict"; /** * ToT — Tree of Thoughts: breadth-first iterative expansion + pruning. * * Paper: "Tree of Thoughts: Deliberate Problem Solving with Large * Language Models" — Yao et al., 2023 * (https://arxiv.org/abs/2305.10601). * * Pattern: Factory → produces a `Runner` built from `Loop(depth times, * Parallel(K thought branches) + prune-to-top-M)`. * Role: patterns/ layer. Pure composition over existing primitives. * Build-time-fixed depth + branching factor; runtime frontier * is pruned to `beamWidth` per level via a consumer-supplied * scorer. * * Tradeoff vs. full DFS: this shipped variant is BFS with constant * width. True DFS with backtracking or adaptive branching factor would * need runtime-variable Parallel (DynamicParallel) or recursion. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.tot = void 0; const LLMCall_js_1 = require("../core/LLMCall.js"); const Loop_js_1 = require("../core-flow/Loop.js"); const Parallel_js_1 = require("../core-flow/Parallel.js"); /** * Build a ToT Runner. At run time: * 1. Seed — treat the input message as the initial frontier of 1 thought. * 2. For each of `depth` iterations: * a. Parallel fan-out: generate `branchingFactor` new thoughts. * b. Score all new thoughts, keep top `beamWidth`, pass to next iteration. * 3. Return the single best-scoring thought from the final frontier. */ function tot(opts) { if (opts.depth < 1) throw new Error('ToT: depth must be >= 1'); if (opts.branchingFactor < 2) { throw new Error('ToT: branchingFactor must be >= 2 (use Reflection for depth-only)'); } const beamWidth = opts.beamWidth ?? 1; if (beamWidth < 1) throw new Error('ToT: beamWidth must be >= 1'); // Each iteration's body is a Parallel fan-out of `branchingFactor` // identical LLMCalls, followed by a scoring-and-pruning pass. The // Parallel branches all receive the same parent-frontier message; // since temperature drives diversity, they produce distinct thoughts. let par = Parallel_js_1.Parallel.create({ id: 'thoughts' }); for (let i = 0; i < opts.branchingFactor; i++) { const thoughtLLM = LLMCall_js_1.LLMCall.create({ provider: opts.provider, model: opts.model, temperature: opts.temperature ?? 0.7, ...(opts.maxTokens !== undefined && { maxTokens: opts.maxTokens }), }) .system(opts.thoughtPrompt) .build(); par = par.branch(`thought-${i}`, thoughtLLM); } // Merge: score all branch outputs, keep top `beamWidth`, concatenate // them as the body's output. Next iteration's LLMCalls will see this // combined survivor text as their input. const parallelStage = par .mergeWithFn((results) => { const ordered = Object.entries(results) .map(([id, thought]) => ({ id, thought, score: opts.score(thought) })) .sort((a, b) => b.score - a.score); const survivors = ordered.slice(0, beamWidth); // Join survivors with a delimiter so the next iteration's LLM can // see the full surviving frontier context. When beamWidth=1 this // is just the winning thought. return survivors.map((s) => s.thought).join('\n\n---\n\n'); }) .build(); // Loop body: the Parallel fan-out + merge. `times(depth)` bounds the // iteration count. Each iteration's output becomes the next // iteration's input automatically (Loop.current = body output). const loop = Loop_js_1.Loop.create({ id: opts.id ?? 'tot', name: opts.name ?? 'ToT', }) .repeat(parallelStage) .times(opts.depth) .build(); // Return loop directly — its final output is the best surviving thought // after `depth` iterations. Wrapping in Sequence would just add an // identity step. For single-thought selection at the end, consumers // can wrap in their own Sequence step. return wrapAsRunner(loop, opts.name ?? 'ToT', opts.id ?? 'tot'); } exports.tot = tot; /** * Identity wrapper so the ToT runner surface stays consistent with * other patterns (factory returns Runner, not Loop directly). Loop is * already a Runner — this is a stable identity passthrough that also * lets us stamp the composition id/name for topology clarity. */ function wrapAsRunner(inner, _name, _id) { // Loop already IS a Runner — return directly. The identity wrapper // would only be needed if we wanted to layer extra events. The // underscore params are future-proofing for adding a seed step. return inner; } //# sourceMappingURL=ToT.js.map