@genkit-ai/ai
Version:
Genkit AI framework generative AI APIs.
1 lines • 23.3 kB
Source Map (JSON)
{"version":3,"sources":["../../src/testing/mock-model.ts"],"sourcesContent":["/**\n * Copyright 2026 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { HasRegistry, Registry } from '@genkit-ai/core/registry';\nimport { Message } from '../message.mjs';\nimport {\n defineModel,\n type GenerateRequest,\n type GenerateResponseChunkData,\n type GenerateResponseData,\n type ModelAction,\n type ModelInfo,\n type Part,\n} from '../model.mjs';\n\n/**\n * A streamed chunk a mock model may emit. A bare string is shorthand for a\n * single text part (`{ content: [{ text }] }`).\n */\nexport type MockChunk = string | GenerateResponseChunkData;\n\n/** Context passed to a {@link MockModelOptions.respond} callback. */\nexport interface MockContext {\n /**\n * Emit a streamed chunk. A no-op unless the caller used `generateStream` /\n * `{ onChunk }`. A string is shorthand for a text part.\n */\n sendChunk: (chunk: MockChunk) => void;\n}\n\n/**\n * What a {@link MockModelOptions.respond} callback may return. From lightest to\n * fullest control:\n * - a `string` — shorthand for a single text response;\n * - an object with any of `text` / `toolRequests` / `content` — the common\n * cases, assembled into a well-formed response for you;\n * - a full {@link GenerateResponseData} (anything with a `message`) — used as-is.\n */\nexport interface MockResponseObject {\n /** Text content of the model message. */\n text?: string;\n /** Tool/function calls to emit, by tool name. */\n toolRequests?: Array<{ name: string; input?: unknown; ref?: string }>;\n /** Escape hatch: raw message parts, prepended before `text`/`toolRequests`. */\n content?: Part[];\n finishReason?: GenerateResponseData['finishReason'];\n usage?: GenerateResponseData['usage'];\n}\n\nexport type MockResponse = string | MockResponseObject | GenerateResponseData;\n\n/** A `respond` callback: given the request, returns the response to emit. */\nexport type MockRespondFn = (\n request: GenerateRequest,\n context: MockContext\n) => MockResponse | Promise<MockResponse>;\n\n/** Options for {@link mockModel}. */\nexport interface MockModelOptions {\n /** Registered model name. Defaults to `'mockModel'`. */\n name?: string;\n /**\n * Model metadata (e.g. `supports`, `versions`).\n *\n * `supports.constrained` defaults to `'all'` (native constrained generation),\n * so a `generate` with an `output.schema` reaches `respond` with that schema\n * on `request.output.schema`. Pass `supports: { constrained: 'none' }` to\n * instead exercise the framework's simulated path, where the schema is\n * injected into the prompt (and thus visible in `lastRequestText`) and\n * stripped from what `respond` sees.\n */\n info?: ModelInfo;\n /**\n * What to respond with on each `generate` call. Defaults to empty text.\n *\n * - A **single response** (string / object) is returned on every call.\n * - A **callback** `(request, { sendChunk }) => response` is invoked once per\n * call, so you can branch on request history (for tool loops) and stream via\n * `sendChunk`.\n * - An **array** is a queue consumed one item per call, with the last item\n * repeating once exhausted — handy for scripted multi-turn tests. A queued\n * `Error` is thrown when reached, to inject a failure on a given turn.\n * (Queued items are static, so streaming needs the callback form.)\n *\n * ```ts\n * mockModel(ai, { respond: 'always this' });\n * mockModel(ai, { respond: ['first', 'second'] });\n * mockModel(ai, { respond: ['ok', new Error('rate limited')] });\n * ```\n */\n respond?: MockRespond;\n}\n\n/**\n * Anything accepted as respond behavior: a single {@link MockResponse}, a\n * per-call callback, or a queue of responses/errors. See\n * {@link MockModelOptions.respond}.\n */\nexport type MockRespond =\n | MockRespondFn\n | MockResponse\n | Array<MockResponse | Error>;\n\n/**\n * A mock model with typed inspection of the calls it received. The extra\n * members are read-only views over the recorded calls.\n */\nexport type MockModel = ModelAction & {\n /** The request from the most recent call, or `undefined` if never called. */\n readonly lastRequest: GenerateRequest | undefined;\n /**\n * The final message of the most recent request, wrapped as a {@link Message}\n * so you can read it the same way you read a response — `.text`, `.media`,\n * `.toolRequests`, etc. `undefined` if the model was never called.\n *\n * ```ts\n * assert.match(model.lastRequestMessage!.text, /Summarize: long text/);\n * ```\n */\n readonly lastRequestMessage: Message | undefined;\n /**\n * The full assembled conversation of the most recent request, flattened to a\n * single string (system + every message, in order). Use it for prompt-\n * assembly assertions on any mock — including ones returning structured\n * output, where {@link echoModel} can't be used. `undefined` if never called.\n *\n * ```ts\n * assert.match(model.lastRequestText!, /system: Be terse/);\n * ```\n */\n readonly lastRequestText: string | undefined;\n /**\n * The tool results fed back to the model in the most recent request, in order.\n * Use it to assert which tools ran and what they returned, without digging\n * through message content yourself. Empty if the model was never called or saw\n * no tool results.\n *\n * ```ts\n * assert.deepStrictEqual(model.toolResponses.map((t) => t.name), ['lookup']);\n * ```\n */\n readonly toolResponses: Array<{\n name: string;\n ref?: string;\n output: unknown;\n }>;\n /** Every request this model received, oldest first. */\n readonly requests: GenerateRequest[];\n /** How many times this model was called. */\n readonly requestCount: number;\n /**\n * Replaces the respond behavior for subsequent calls. Accepts everything\n * {@link MockModelOptions.respond} does; an array re-arms as a fresh queue.\n * Recorded history is untouched — use {@link MockModel.reset} for that.\n *\n * Together with `reset()` this supports the register-once idiom: define the\n * mock once per test file, then give each test its own behavior.\n *\n * ```ts\n * const model = mockModel(ai, { name: 'menuModel' });\n * beforeEach(() => model.reset());\n *\n * test('...', async () => {\n * model.respondWith({ text: 'scripted' });\n * // ...\n * });\n * ```\n */\n respondWith(respond: MockRespond): void;\n /**\n * Clears recorded history (`requests`, `requestCount`, …) and restores the\n * respond behavior given at construction, re-arming a queued `respond` from\n * its first item. Call it in `beforeEach` so tests sharing a mock stay\n * order-independent.\n */\n reset(): void;\n};\n\nfunction resolveRegistry(registry: Registry | HasRegistry): Registry {\n return (registry as HasRegistry).registry ?? (registry as Registry);\n}\n\n/**\n * Snapshots a request so later mutation can't alter recorded history. Prefers a\n * deep `structuredClone`, but falls back to a message/part-level copy when the\n * request carries non-serializable values (e.g. a function or class instance in\n * `config`), which would otherwise throw a `DataCloneError`.\n */\nfunction cloneRequest(request: GenerateRequest): GenerateRequest {\n try {\n return structuredClone(request);\n } catch {\n return {\n ...request,\n messages: request.messages.map((m) => ({\n ...m,\n content: m.content.map((c) => ({ ...c })),\n })),\n };\n }\n}\n\nfunction toChunkData(chunk: MockChunk): GenerateResponseChunkData {\n return typeof chunk === 'string' ? { content: [{ text: chunk }] } : chunk;\n}\n\n/**\n * Renders a single request part to text for {@link renderRequestText}. Non-text\n * parts (media, tool requests/responses, reasoning, resource, data, custom) are\n * rendered as a labelled placeholder rather than silently dropped.\n */\nfunction renderPart(part: Part): string {\n if (part.text !== undefined) return part.text;\n if (part.media) {\n const type = part.media.contentType ? ` ${part.media.contentType}` : '';\n return `[media${type}: ${part.media.url}]`;\n }\n if (part.toolRequest) {\n const { name, input } = part.toolRequest;\n return `[toolRequest ${name}(${JSON.stringify(input)})]`;\n }\n if (part.toolResponse) {\n const { name, output } = part.toolResponse;\n return `[toolResponse ${name}: ${JSON.stringify(output)}]`;\n }\n if (part.reasoning !== undefined) return `[reasoning: ${part.reasoning}]`;\n if (part.resource) return `[resource: ${part.resource.uri}]`;\n if (part.data !== undefined) return `[data: ${JSON.stringify(part.data)}]`;\n if (part.custom !== undefined)\n return `[custom: ${JSON.stringify(part.custom)}]`;\n return '';\n}\n\n/**\n * Flattens a request's full message list to text — system and tool messages are\n * prefixed with their role; `user`/`model` are not. Messages are newline-\n * separated so adjacent messages' text can't fuse into a single token (which\n * would silently break boundary-spanning assertions). This is the assembled\n * conversation the model would have seen, used by both {@link echoModel} and\n * {@link MockModel.lastRequestText}.\n */\nfunction renderRequestText(request: GenerateRequest): string {\n return request.messages\n .map(\n (m) =>\n (m.role === 'user' || m.role === 'model' ? '' : `${m.role}: `) +\n m.content.map(renderPart).join('')\n )\n .join('\\n');\n}\n\n/**\n * Normalizes the `respond` option into a single callback. A single response is\n * returned on every call; an array becomes a queue consumed one item per call,\n * with the last item repeating once exhausted; a queued `Error` is thrown when\n * reached.\n */\nfunction toRespondFn(respond: MockRespond | undefined): MockRespondFn {\n if (respond === undefined) {\n return () => ({ text: '' });\n }\n if (typeof respond === 'function') {\n return respond;\n }\n const queue = Array.isArray(respond) ? respond : [respond];\n if (queue.length === 0) {\n return () => ({ text: '' });\n }\n let i = 0;\n return () => {\n const item = queue[Math.min(i, queue.length - 1)];\n i++;\n if (item instanceof Error) throw item;\n return item;\n };\n}\n\nfunction toResponseData(response: MockResponse): GenerateResponseData {\n // A `respond` that streams but returns nothing (void) yields an empty message\n // rather than throwing on the `'message' in response` check below. Checked\n // against null/undefined only, so `respond: ''` still means empty *text*.\n if (response === undefined || response === null) {\n return { message: { role: 'model', content: [] }, finishReason: 'stop' };\n }\n if (typeof response === 'string') {\n return {\n message: { role: 'model', content: [{ text: response }] },\n finishReason: 'stop',\n };\n }\n if ('message' in response && response.message) {\n const data = response as GenerateResponseData;\n // Default finishReason without letting an explicit `undefined` clobber it.\n return { ...data, finishReason: data.finishReason ?? 'stop' };\n }\n const obj = response as MockResponseObject;\n const content: Part[] = [...(obj.content ?? [])];\n if (obj.text !== undefined) {\n content.push({ text: obj.text });\n }\n for (const tool of obj.toolRequests ?? []) {\n content.push({\n toolRequest: { name: tool.name, input: tool.input, ref: tool.ref },\n });\n }\n return {\n message: { role: 'model', content },\n finishReason: obj.finishReason ?? 'stop',\n usage: obj.usage,\n };\n}\n\n/**\n * Defines a programmable mock model for tests. Drive each call's response with\n * `respond`, and inspect what the model was called with via `lastRequest` /\n * `requests` / `requestCount`.\n *\n * ```ts\n * const model = mockModel(ai, { respond: () => ({ text: 'a summary' }) });\n * const out = (await ai.generate({ model, prompt: 'Summarize: ...' })).text;\n * assert.match(model.lastRequest!.messages.at(-1)!.content[0].text!, /Summarize/);\n * ```\n *\n * Streaming and tool calls are first-class:\n *\n * ```ts\n * mockModel(ai, {\n * respond: (req, { sendChunk }) => {\n * sendChunk('hel');\n * sendChunk('lo');\n * return { text: 'hello' };\n * },\n * });\n *\n * mockModel(ai, {\n * respond: () => ({ toolRequests: [{ name: 'lookup', input: { id: 1 } }] }),\n * });\n * ```\n *\n * For structured output, the mock defaults to native constrained generation\n * (`supports.constrained: 'all'`), so `respond` sees `request.output.schema`\n * and no schema blob is injected into the prompt. Override with\n * `supports: { constrained: 'none' }` to test the simulated path.\n *\n * @param registry a `Genkit` instance (or anything holding a `Registry`).\n * @param options model name, metadata, and the `respond` callback.\n */\nexport function mockModel(\n registry: Registry | HasRegistry,\n options: MockModelOptions = {}\n): MockModel {\n const requests: GenerateRequest[] = [];\n let respond = toRespondFn(options.respond);\n\n const model = defineModel(\n resolveRegistry(registry),\n {\n apiVersion: 'v2',\n name: options.name ?? 'mockModel',\n // Forward only the metadata fields defineModel accepts; ModelInfo's\n // `configSchema`/`stage` aren't part of DefineModelOptions.\n versions: options.info?.versions,\n label: options.info?.label,\n // Default to native constrained generation (like modern provider models),\n // so a structured-output request reaches `respond` with `output.schema`\n // intact instead of the framework injecting a schema blob into the prompt\n // and stripping it. Spread last so callers can opt out with\n // `supports: { constrained: 'none' }` to exercise the simulated path.\n supports: { constrained: 'all', ...options.info?.supports },\n },\n async (request, { sendChunk }) => {\n // Snapshot so later mutation of the request can't alter recorded history.\n requests.push(cloneRequest(request));\n const context: MockContext = {\n sendChunk: (chunk) => sendChunk?.(toChunkData(chunk)),\n };\n return toResponseData(await respond(request, context));\n }\n ) as MockModel;\n\n Object.defineProperties(model, {\n // Return clones so callers can't mutate recorded history through a view.\n requests: { get: () => requests.map((r) => cloneRequest(r)) },\n lastRequest: {\n get: () => {\n const last = requests[requests.length - 1];\n return last ? cloneRequest(last) : undefined;\n },\n },\n lastRequestMessage: {\n get: () => {\n const last = requests[requests.length - 1]?.messages.at(-1);\n return last ? new Message(last) : undefined;\n },\n },\n lastRequestText: {\n get: () => {\n const last = requests[requests.length - 1];\n return last ? renderRequestText(last) : undefined;\n },\n },\n toolResponses: {\n get: () =>\n (requests[requests.length - 1]?.messages ?? [])\n .flatMap((m) => m.content)\n .filter((p) => p.toolResponse)\n .map((p) => ({\n name: p.toolResponse!.name,\n ref: p.toolResponse!.ref,\n output: p.toolResponse!.output,\n })),\n },\n requestCount: { get: () => requests.length },\n respondWith: {\n value: (next: MockRespond) => {\n respond = toRespondFn(next);\n },\n },\n reset: {\n value: () => {\n requests.length = 0;\n respond = toRespondFn(options.respond);\n },\n },\n });\n return model;\n}\n\n/** Options for {@link echoModel}. */\nexport interface EchoModelOptions {\n /** Registered model name. Defaults to `'echoModel'`. */\n name?: string;\n /** Model metadata. */\n info?: ModelInfo;\n}\n\n/**\n * A {@link mockModel} preset for *text* paths: a zero-config model that echoes\n * the rendered request back as text, for asserting prompt and message assembly\n * (what the model *would have seen*). Supports the same inspection members as\n * {@link mockModel}.\n *\n * ```ts\n * const model = echoModel(ai);\n * const res = await ai.generate({ model, system: 'Be terse', prompt: 'hi' });\n * assert.match(res.text, /system: Be terse/);\n * ```\n *\n * Because it returns text, it can't satisfy a structured **output schema** —\n * Genkit derives `output` by parsing the response text and validating it, which\n * prose can't pass. If the request carries an output schema, `echoModel` throws\n * an explanatory error. For structured-output paths, use {@link mockModel} with\n * a conforming response and assert assembly via {@link MockModel.lastRequestText}\n * / {@link MockModel.lastRequest} instead.\n *\n * @param registry a `Genkit` instance (or anything holding a `Registry`).\n * @param options model name and metadata.\n */\nexport function echoModel(\n registry: Registry | HasRegistry,\n options: EchoModelOptions = {}\n): MockModel {\n return mockModel(registry, {\n name: options.name ?? 'echoModel',\n // Declare native constrained support so the framework hands the output\n // schema to the model directly (in `request.output.schema`) rather than\n // injecting it as prompt text — that lets the guard below detect it\n // reliably, and keeps the echo free of framework-injected schema blobs.\n info: {\n ...options.info,\n supports: { ...options.info?.supports, constrained: 'all' },\n },\n respond: (request) => {\n if (request.output?.schema) {\n throw new Error(\n \"echoModel returns text and can't satisfy an output schema: this \" +\n 'request asks for structured output. Either move `output: { schema }` ' +\n 'to the generate()/flow call site so the prompt stays text-only, or ' +\n 'use mockModel(...) with a conforming response and assert prompt ' +\n 'assembly via model.lastRequestText / model.lastRequest.'\n );\n }\n return {\n content: [\n { text: 'Echo: ' + renderRequestText(request) },\n { text: '; config: ' + JSON.stringify(request.config) },\n ],\n };\n },\n });\n}\n"],"mappings":"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","names":[]}