agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
79 lines (78 loc) • 4.14 kB
TypeScript
/**
* IntentScorer — the pluggable PORT that judges a new user message against the
* graph's declared intents (SG-C tier 2).
*
* Custody split, deliberately: a scorer produces NUMBERS for EVERY candidate —
* never a bare winner — and the FRAMEWORK (`routingPolicy.decideTier2`)
* applies the floor and the near-tie margin, decides the verdict, and records
* it. That is what keeps scorers a tier and never a correctness dependency: a
* scorer cannot route by itself, and its near-ties fall through to the model.
*
* Three built-ins share this port:
* • `keywordScorer()` — widened in `entryScorer.ts` (no dependency;
* set-cosine over intent + example tokens; `floor: 0` — zero overlap IS
* honestly "did not match at all");
* • `embeddingScorer(e)` — widened in `entryScorer.ts` (semantic cosine;
* NO floor unless the consumer declares one — an absolute embedding
* threshold is a lie by default);
* • `llmClassifier(provider)` — `llmClassifier.ts` (one constrained-enum
* model call per turn, off the hot loop; `categorical`).
*
* `when` predicates remain the opaque escape hatch — tier 1, never scored.
*/
/** One candidate the classifier judges — a declared intent, or the incumbent
* skill (its `intent` = its description; examples may be empty). Ids are
* never fabricated — every candidate comes from the graph. */
export interface IntentCandidate {
readonly id: string;
/** One sentence naming the intent ("customer wants a refund"). */
readonly intent: string;
/** Real user phrasings. May be empty only for the incumbent. */
readonly examples: readonly string[];
}
/** What a scorer receives. `recentTurns` is present ONLY when the scorer
* declared a `window` — newest last, the current message excluded. */
export interface IntentScorerInput {
/** The NEW user message that started this turn. */
readonly message: string;
readonly recentTurns?: ReadonlyArray<{
readonly role: 'user' | 'assistant';
readonly content: string;
}>;
}
/** One candidate's raw, strategy-specific score — same contract as
* `EntryScore.score`: higher = more relevant, not normalized across
* strategies. The framework softmaxes for the recorded relevance shares. */
export interface IntentScore {
readonly id: string;
readonly score: number;
}
/**
* The port. Score EVERY candidate — the framework refuses a result that omits
* a candidate or names an id it wasn't given (see {@link validateIntentScores}).
* Runs ONCE per turn, off the hot loop, in the RouteTurn stage.
*/
export interface IntentScorer {
/** Short stable name — lands on the record (`turn_routed.scorer`). */
readonly name: string;
/** Declared floor: a raw score at/below it means "did not match at all".
* Absent = this scorer cannot honestly claim non-match (embeddings). */
readonly floor?: number;
/** Declared recent-turns window (count of prior conversational turns the
* scorer wants to see). Absent = current message only. */
readonly window?: number;
/** One id or none (the LLM classifier): a pick is decisive by construction
* and is never diluted by the near-tie margin. */
readonly categorical?: boolean;
score(input: IntentScorerInput, candidates: readonly IntentCandidate[], signal?: AbortSignal): Promise<readonly IntentScore[]> | readonly IntentScore[];
}
/**
* The framework's half of the scorer contract: every candidate scored exactly
* once, no foreign ids. Returns the scores in CANDIDATE order (so a scorer
* that reordered them cannot smuggle a different ranking past the caller's
* declaration order). Throws a teaching error naming the scorer, what it
* omitted or invented, and the contract — the RouteTurn stage treats that
* throw exactly like a scorer failure (fall through to the recorded fallback),
* because a misbehaving scorer must never abort a run.
*/
export declare function validateIntentScores(scorerName: string, candidates: readonly IntentCandidate[], result: readonly IntentScore[]): readonly IntentScore[];