UNPKG

@adonis-agora/agent

Version:

Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.

179 lines • 7.77 kB
import type { ModelProvider } from '../../spi/model-provider.js'; /** * The structured side of every tree-LLM request: what is being asked, as data. The prompt says the * same thing in prose; `task` exists so a deterministic fake (tests, offline dev — see * {@link import('./keyword-tree-llm.js').keywordTreeLlm}) can answer without parsing prose, and so a * host can log or route requests by kind. */ export type TreeLlmTask = { kind: 'structure'; /** The window of pages the LLM is asked to find section starts in. */ units: { unit: number; page?: number; text: string; }[]; } | { kind: 'summarize'; title: string; text: string; } | { kind: 'describe'; title?: string; outline: { title: string; summary?: string; }[]; } | { kind: 'navigate'; query: string; /** `single-pass`: the whole tree is shown; `beam`: one level of candidates at a time. */ mode: 'single-pass' | 'beam'; candidates: TreeLlmCandidate[]; /** Max nodes the answer may name. */ maxNodes: number; } | { kind: 'select'; query: string; documents: { id: string; title?: string; description?: string; }[]; maxDocuments: number; }; /** A node as shown to the navigating LLM. */ export interface TreeLlmCandidate { id: string; title: string; summary?: string; pages?: string; /** Present on beam candidates: whether the node can be expanded. */ hasChildren?: boolean; /** Present in single-pass mode: depth (0 = top level). */ depth?: number; } export interface TreeLlmRequest { task: TreeLlmTask; system: string; prompt: string; /** A cap for the reply. Adapters pass it as `max_tokens` when they can. */ maxOutputTokens: number; /** Aborted when the call's timeout or the build/navigation deadline passes. */ signal: AbortSignal; } export interface TreeLlmResponse { text: string; /** Provider-reported usage. When absent, the budget estimates ~4 characters per token. */ usage?: { inputTokens?: number; outputTokens?: number; }; } /** * The only LLM surface tree building and navigation need: one prompt in, one text reply out. Replies * are expected to be JSON (for structure / navigate / select) or plain text (summaries); parsing is * lenient (code fences, prose around the object). Bring any model: * {@link treeLlmFromModelProvider} (the agent's own `ModelProvider`), {@link openAiChatTreeLlm} * (any OpenAI-compatible `/chat/completions`), or a function of your own. Use a deterministic * setting (temperature 0) — trees are cached by content, so a flaky model makes rebuilds noisy. */ export type TreeLlm = (request: TreeLlmRequest) => Promise<TreeLlmResponse>; /** * Hard limits for one document build or one navigation. Every LLM call is checked against them * **before** it is made (with the prompt's estimated size), so a budget is never overshot by more * than one reply, and nothing retries past it: the failure mode this exists for is an indexer that * burned ~1,240 calls on one PDF it could not structure. */ export interface TreeBudget { /** Max LLM calls. */ maxCalls?: number; /** Max input (prompt) tokens across all calls, estimated at ~4 chars/token before each call. */ maxInputTokens?: number; /** Max output tokens across all calls. */ maxOutputTokens?: number; /** Wall-clock deadline for the whole operation, ms. */ timeoutMs?: number; /** Per-call timeout, ms. Default: whatever is left of `timeoutMs`. */ callTimeoutMs?: number; } /** Thrown internally when a budget or deadline would be exceeded; callers turn it into a fallback. */ export declare class TreeBudgetExceededError extends Error { readonly limit: 'calls' | 'inputTokens' | 'outputTokens' | 'timeout'; constructor(limit: 'calls' | 'inputTokens' | 'outputTokens' | 'timeout'); } /** ~4 characters per token: the budget's estimate when a provider reports no usage. */ export declare function estimateTokens(text: string): number; /** Tracks spend against a {@link TreeBudget} and runs calls under it. Not exported from the package. */ export declare class BudgetedLlm { private readonly llm; private readonly budget; private readonly outerSignal?; calls: number; inputTokens: number; outputTokens: number; exhausted: boolean; private readonly deadline; constructor(llm: TreeLlm, budget: Required<Pick<TreeBudget, 'maxCalls' | 'maxInputTokens' | 'maxOutputTokens'>> & TreeBudget, outerSignal?: AbortSignal | undefined); /** Time left before the deadline, ms (`Infinity` without one). */ remainingMs(): number; /** Would a call with this prompt fit? Does not reserve anything; a `false` marks the budget exhausted. */ fits(prompt: string, maxOutputTokens: number): boolean; private limitHit; /** * Make one call, or throw {@link TreeBudgetExceededError} without calling. Provider errors * propagate (after being counted). Never retries. */ call(task: TreeLlmTask, system: string, prompt: string, maxOutputTokens: number): Promise<string>; } /** * Pull the JSON object out of a model reply: bare, fenced in ```json, or surrounded by prose. * Returns `undefined` when there is none (never throws). */ export declare function parseJsonReply(text: string): Record<string, unknown> | undefined; /** The strings in a JSON array value (numbers stringified); anything else yields `[]`. */ export declare function stringList(value: unknown): string[]; /** * A {@link TreeLlm} over the agent's own {@link ModelProvider}: one `runTurn` with no tools and a * discarding sink. Usage comes from the provider's report. */ export declare function treeLlmFromModelProvider(provider: ModelProvider): TreeLlm; export interface OpenAiChatTreeLlmOptions { model: string; /** Base URL up to and including `/v1`. Default `https://api.openai.com/v1`. */ baseUrl?: string; apiKey?: string; headers?: Record<string, string>; /** Default 0 — trees are cached by content, determinism keeps rebuilds quiet. */ temperature?: number; /** * Send `response_format: { type: 'json_object' }` on JSON tasks. Default `false`: not every * OpenAI-compatible server accepts it, and replies are parsed leniently anyway. */ jsonMode?: boolean; /** Extra body fields (a reasoning switch, provider routing). */ body?: Record<string, unknown>; fetch?: typeof fetch; } /** * A {@link TreeLlm} over any OpenAI-compatible `POST /v1/chat/completions` — OpenAI, a gateway, * vLLM, Ollama, DeepSeek, Qwen… — with no SDK dependency. Throws `HttpModelError` on a non-2xx. */ export declare function openAiChatTreeLlm(options: OpenAiChatTreeLlmOptions): TreeLlm; /** A get/set cache — a `Map`, an LRU, a Redis wrapper. Values are whole replies. */ export interface TreeLlmCache { get(key: string): TreeLlmResponse | undefined | Promise<TreeLlmResponse | undefined>; set(key: string, value: TreeLlmResponse): unknown; } /** * Memoize a {@link TreeLlm} by the exact request (task kind, system prompt, prompt, output cap). * With a deterministic model that makes a rebuild of an unchanged section, or a repeated question, * free. Cached replies report zero usage, so they cost nothing against a budget but a call slot. */ export declare function cachedTreeLlm(llm: TreeLlm, cache?: TreeLlmCache): TreeLlm; /** * A fast, deterministic, non-cryptographic 64-bit hash (two independent 32-bit FNV-1a/murmur-mixed * lanes) as 16 hex chars — content fingerprints and cache keys, not security. */ export declare function stableHash(text: string): string; //# sourceMappingURL=llm.d.ts.map