agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
60 lines • 3.17 kB
TypeScript
/**
* constrainedEnumPick — ONE model call that can only answer from a fixed
* list (9.19.0). The machinery extracted from `llmClassifier` so the tier-3
* decider (RouteTurn's out-of-band menu resolver) and the classifier share
* one enum discipline instead of two drifting copies:
*
* • When `forcedToolChoiceFor(provider, model)` permits it, the pick rides
* a forced synthetic tool whose single argument is `enum: allowed` —
* the same mechanism the `'tool-forced'` output strategy uses. The
* provider constrains generation; an answer outside the enum cannot be
* emitted at all.
* • On any other provider it is a strict single-line parse validated
* against the enum, ONE structured re-ask, then the caller's fallback.
*
* An off-enum answer is a PARSE FAILURE, never a pick — this function can
* never return an id it was not given (the `allowed` list plus `fallback`).
* Callers put their own "none" sentinel INTO `allowed` (and as `fallback`)
* so the model can decline explicitly; the sentinel is a first-class enum
* member on the wire, exactly as `llmClassifier` has always sent it.
*/
import type { LLMProvider } from '../provider-contract/types.js';
/** The synthetic pick tool, named by the caller (the wire shows this name). */
export interface EnumPickTool {
readonly name: string;
readonly description: string;
/** The single argument's name (`'intent'` for the classifier, `'skill'`
* for the decider) — a reader of the raw request sees the domain word. */
readonly argName: string;
readonly argDescription: string;
}
export interface ConstrainedEnumPickRequest {
readonly provider: LLMProvider;
/** Model id for the call. Left unset, the request carries an empty model
* id, which only the mock tolerates — name it for any real provider. */
readonly model?: string;
/** The catalog/context prompt — WITHOUT the parse instruction; the parse
* path appends its own, so both paths read one catalog. */
readonly systemPrompt: string;
readonly messages: ReadonlyArray<{
readonly role: 'user' | 'assistant';
readonly content: string;
}>;
/** The complete enum, the caller's decline-sentinel included. */
readonly allowed: readonly string[];
/** Returned on parse failure / off-enum after the one re-ask (callers pass
* their decline-sentinel, conventionally `'none'`). */
readonly fallback: string;
readonly pickTool: EnumPickTool;
readonly signal?: AbortSignal;
}
/** Run the pick. Resolves to a member of `allowed`, else `fallback`. */
export declare function constrainedEnumPick(req: ConstrainedEnumPickRequest): Promise<string>;
/**
* A trimmed single-token answer that IS one of the allowed ids, else undefined.
* The decoration is walked off each end, not matched with `/[…]+$/`: the
* reply is model output, and that regex retries from every position of a long
* run of quotes (`lib/linearText.ts` · "Why these exist").
*/
export declare function parseEnumLine(content: string, allowed: readonly string[]): string | undefined;
//# sourceMappingURL=constrainedEnumPick.d.ts.map