@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
137 lines • 6.14 kB
TypeScript
import type { Passage, RetrieveOptions, Retriever } from '../../spi/retriever.js';
import { type TreeBudget, type TreeLlm } from './llm.js';
import type { DocumentTreeStore } from './store.js';
export interface TreeNavigationRetrieverOptions {
store: DocumentTreeStore;
llm: TreeLlm;
/**
* A tree whose outline (titles, pages, summaries) fits in this many characters is navigated in
* **one** call that sees the whole outline — PageIndex's tree search, the variant measured in the
* benchmark. Bigger trees are walked level by level (a beam). Default 40 000 (~10k tokens).
*/
singlePassMaxChars?: number;
/** Nodes the navigator may expand or read per beam step. Default 4. */
beamWidth?: number;
/** Beam levels, at most. Nodes still marked "expand" at the last level are read whole. Default 4. */
maxSteps?: number;
/** Nodes returned per document, at most. Default 5. */
maxNodes?: number;
/**
* Hard per-document limits for one navigation. Defaults: 6 calls, 60k input tokens, 4k output
* tokens, 60 s. Running out mid-walk returns what was chosen so far (the nodes last marked for
* expansion are read whole) and records `stoppedBy: 'budget'`.
*/
budget?: TreeBudget;
/** Characters of text per returned node. Default 12 000. */
maxPassageChars?: number;
/** Units (pages/sections) read per returned node, at most. Default 25. */
maxUnitsPerNode?: number;
/** Characters across all passages of one navigation (every document). Default 48 000 (~12k tokens). */
maxContextChars?: number;
/**
* {@link TreeNavigationRetriever.retrieve} with no first stage: documents navigated per query. When
* more trees pass the filter, the LLM picks this many from their titles and descriptions
* (PageIndex's document selection). Default 3.
*/
maxDocuments?: number;
/** Trees shown to document selection, at most. Default 200. */
maxSelectionCandidates?: number;
/** Documents navigated in parallel. Default 3. */
concurrency?: number;
}
/** One step of a navigation, for the audit trail. */
export interface NavigationStep {
step: number;
/** Node ids shown to the LLM this step. */
shown: string[];
/** Node ids it chose to open further. */
expanded: string[];
/** Node ids it chose to read. */
read: string[];
/** The LLM's stated reasoning, verbatim (capped). */
reasoning?: string;
}
/** A node navigation chose, with the path that led to it. */
export interface NavigatedNode {
nodeId: string;
title: string;
/** Ancestors from the top level down, then the node itself. */
path: {
id: string;
title: string;
}[];
pageStart?: number;
pageEnd?: number;
unitStart: number;
unitEnd: number;
}
/** The auditable record of navigating one document. */
export interface DocumentNavigation {
documentId: string;
title?: string;
mode: 'single-pass' | 'beam';
steps: NavigationStep[];
nodes: NavigatedNode[];
llmCalls: number;
inputTokens: number;
outputTokens: number;
/** Why the walk stopped early, when it did. */
stoppedBy?: 'budget' | 'timeout' | 'maxSteps' | 'error' | 'not-found';
error?: string;
}
export interface NavigationResult {
passages: Passage[];
documents: DocumentNavigation[];
/** Set when {@link TreeNavigationRetriever.retrieve} had the LLM pick the documents. */
selection?: {
candidates: number;
selected: string[];
reasoning?: string;
error?: string;
};
}
export interface NavigateOptions {
/** Access filter applied to every tree read (vector-store semantics). */
filter?: Record<string, unknown>;
/** Overrides the retriever's per-document budget. */
budget?: TreeBudget;
signal?: AbortSignal;
}
/**
* Tree navigation retrieval ("vectorless", reasoning-based — PageIndex's method): an LLM reads a
* document's table of contents (titles, page ranges, summaries — no body text) and picks the
* sections that answer the question; their text comes back as passages, with the path taken and the
* model's reasoning recorded so the choice is auditable.
*
* A small tree is navigated in one call over the whole outline. A large one is walked level by
* level: the model sees one level of candidates, opens (`expand`) the promising ones and takes
* (`read`) the ones that already answer, until it reaches leaves, runs out of `maxSteps`, or runs
* out of its hard per-document {@link TreeBudget} — in which case it returns what it had.
*
* It pays off on **long structured documents** (regulations, contracts, filings, manuals) where
* chunk ranking struggles among hundreds of look-alike passages; on short notes it costs seconds and
* LLM spend for nothing. Compose it behind a first stage with {@link TwoStageRetriever}, or give it
* to the agent as a tool with {@link createNavigateDocumentTool}.
*
* As a plain `Retriever`, `retrieve(query, { filter })` navigates the trees that pass `filter`:
* all of them when there are at most `maxDocuments`, otherwise the ones the LLM picks from their
* descriptions.
*/
export declare class TreeNavigationRetriever implements Retriever {
private readonly options;
readonly store: DocumentTreeStore;
private readonly llm;
constructor(options: TreeNavigationRetrieverOptions);
retrieve(query: string, options?: RetrieveOptions): Promise<Passage[]>;
/** {@link retrieve} with the audit trail: which documents, which nodes, why. */
search(query: string, options?: NavigateOptions): Promise<NavigationResult>;
/**
* Navigate the given documents (in parallel, up to `concurrency`) and return their passages in
* the order given, within `maxContextChars`. Documents without a tree, or whose tree fails
* `filter`, yield no passages and `stoppedBy: 'not-found'`.
*/
navigate(query: string, documentIds: readonly string[], options?: NavigateOptions): Promise<NavigationResult>;
private walk;
private passagesOf;
}
//# sourceMappingURL=navigate.d.ts.map