UNPKG

agentfootprint

Version:

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

126 lines • 5.15 kB
"use strict"; /** * outputSchema — declarative terminal contract for an Agent's final answer. * * The Block A6 piece. Lets a consumer say: * * "Whatever this agent says at the end of a run, it MUST be JSON * matching this Zod (or Zod-like) schema. Auto-instruct the LLM, * parse + validate the final answer for me." * * Why a typed contract matters: agentic code that calls an LLM and * then JSON.parses the string answer is brittle. The schema serves * three jobs at once: * * 1. **Instruction**: a system-prompt sentence telling the LLM the * output shape (auto-generated from `schema.description` / * JSON-schema introspection where available, or from a consumer- * supplied `instruction` override). * * 2. **Validation**: a `parser.parse(rawString)` step on the run's * final answer, throwing `OutputSchemaError` on parse / shape * failure rather than returning malformed data. * * 3. **Type narrowing**: `agent.runTyped({...})` returns the inferred * shape `T` instead of `string`, so callers stop reaching for * `as MyType` casts. * * Pattern: Strategy (GoF) over the parser interface; structural * duck-typing (Zod / Valibot / ArkType / hand-written — * anything with a `parse(unknown): T` method satisfies it). * * Role: Layer-6 (Agent) — terminal contract on the run output. * NOT a context-engineering Injection per se, but composes * with the InjectionEngine (auto-injects an Instruction). * * @example zod schema * import { z } from 'zod'; * const Output = z.object({ status: z.enum(['ok', 'err']), items: z.array(z.string()) }); * * const agent = Agent.create({ ... }) * .system('You answer support tickets.') * .outputSchema(Output) * .build(); * * const typed = await agent.runTyped({ message: 'list pending tickets' }); * typed.status; // narrowed to 'ok' | 'err' * * @example valibot / arktype / hand-written parser * const Output = { parse(v: unknown): MyType { ... } }; * const agent = Agent.create({...}).outputSchema(Output, { instruction: '...' }).build(); */ Object.defineProperty(exports, "__esModule", { value: true }); exports.applyOutputSchema = exports.buildDefaultInstruction = exports.OutputSchemaError = void 0; /** * Thrown by `agent.parseOutput(...)` / `agent.runTyped(...)` when the * agent's final answer fails JSON parsing OR schema validation. * * `cause` carries the underlying parse error (Zod's ZodError, etc.). * `rawOutput` carries the agent's untyped string output so callers * can log / persist the failed response for triage. */ class OutputSchemaError extends Error { rawOutput; stage; cause; constructor(message, opts) { super(message); this.name = 'OutputSchemaError'; this.rawOutput = opts.rawOutput; this.stage = opts.stage; if (opts.cause !== undefined) this.cause = opts.cause; } } exports.OutputSchemaError = OutputSchemaError; /** * Default instruction template — used when `opts.instruction` is not * provided. Concatenates the parser's `.description` (if present) so * Zod schemas authored with `.describe('...')` propagate naturally. */ function buildDefaultInstruction(parser) { const tail = parser.description ? ` The output shape: ${parser.description}.` : ''; return ('Respond ONLY with valid JSON matching the output schema. ' + 'Do NOT include prose, markdown fences, or explanatory text.' + tail); } exports.buildDefaultInstruction = buildDefaultInstruction; /** Remove one whole-answer JSON/unlabelled fence, never prose or JSON content. * A fence has its own opening and closing lines. Any nested or additional * wrapper remains in the body and is rejected by the strict JSON parser. */ function jsonAnswerText(raw) { const fenced = /^[ \t\r\n]*```[ \t]*(?:json[ \t]*)?\r?\n([\s\S]*?)\r?\n[ \t]*```[ \t\r\n]*$/i.exec(raw); return fenced?.[1] ?? raw; } /** * Parse + validate a raw string answer against a parser. Used by * `agent.parseOutput()` / `agent.runTyped()`. Centralized here so * both call sites share identical error-mapping behavior. * * Two-stage error reporting: * - JSON parse failure → `stage: 'json-parse'` (LLM emitted prose * or malformed JSON) * - Schema validation failure → `stage: 'schema-validate'` (JSON * was valid but didn't match the contracted shape) */ function applyOutputSchema(raw, parser) { let parsed; try { parsed = JSON.parse(jsonAnswerText(raw)); } catch (cause) { throw new OutputSchemaError('Agent final answer is not valid JSON. The LLM emitted prose or malformed JSON.', { rawOutput: raw, stage: 'json-parse', cause }); } try { return parser.parse(parsed); } catch (cause) { throw new OutputSchemaError('Agent final answer parsed as JSON but failed schema validation.', { rawOutput: raw, stage: 'schema-validate', cause, }); } } exports.applyOutputSchema = applyOutputSchema; //# sourceMappingURL=outputSchema.js.map