@genkit-ai/ai
Version:
Genkit AI framework generative AI APIs.
1 lines • 12.6 kB
Source Map (JSON)
{"version":3,"sources":["../../src/model/middleware.ts"],"sourcesContent":["/**\n * Copyright 2024 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 { Document } from '../document.js';\nimport { injectInstructions } from '../formats/index.js';\nimport type {\n MediaPart,\n MessageData,\n ModelInfo,\n ModelMiddleware,\n Part,\n} from '../model.js';\n/**\n * Preprocess a GenerateRequest to download referenced http(s) media URLs and\n * inline them as data URIs.\n */\nexport function downloadRequestMedia(options?: {\n maxBytes?: number;\n filter?: (part: MediaPart) => boolean;\n}): ModelMiddleware {\n return async (req, next) => {\n const { default: fetch } = await import('node-fetch');\n\n const newReq = {\n ...req,\n messages: await Promise.all(\n req.messages.map(async (message) => {\n const content: Part[] = await Promise.all(\n message.content.map(async (part) => {\n // skip non-media parts and non-http urls, or parts that have been\n // filtered out by user config\n if (\n !part.media ||\n !part.media.url.startsWith('http') ||\n (options?.filter && !options?.filter(part))\n ) {\n return part;\n }\n\n const response = await fetch(part.media.url, {\n size: options?.maxBytes,\n });\n if (response.status !== 200)\n throw new Error(\n `HTTP error downloading media '${\n part.media.url\n }': ${await response.text()}`\n );\n\n // use provided contentType or sniff from response\n const contentType =\n part.media.contentType ||\n response.headers.get('content-type') ||\n '';\n\n return {\n media: {\n contentType,\n url: `data:${contentType};base64,${Buffer.from(\n await response.arrayBuffer()\n ).toString('base64')}`,\n },\n };\n })\n );\n\n return {\n ...message,\n content,\n };\n })\n ),\n };\n\n return next(newReq);\n };\n}\n\n/**\n * Validates that a GenerateRequest does not include unsupported features.\n */\nexport function validateSupport(options: {\n name: string;\n supports?: ModelInfo['supports'];\n}): ModelMiddleware {\n const supports = options.supports || {};\n return async (req, next) => {\n function invalid(message: string): never {\n throw new Error(\n `Model '${\n options.name\n }' does not support ${message}. Request: ${JSON.stringify(\n req,\n null,\n 2\n )}`\n );\n }\n\n if (\n supports.media === false &&\n req.messages.some((message) => message.content.some((part) => part.media))\n )\n invalid('media, but media was provided');\n if (supports.tools === false && req.tools?.length)\n invalid('tool use, but tools were provided');\n if (supports.multiturn === false && req.messages.length > 1)\n invalid(`multiple messages, but ${req.messages.length} were provided`);\n // if (\n // typeof supports.output !== 'undefined' &&\n // req.output?.format &&\n // !supports.output.includes(req.output?.format)\n // )\n // invalid(`requested output format '${req.output?.format}'`);\n return next();\n };\n}\n\n// N.B. Figure out why array.findLast isn't available despite setting target\n// to ES2022 (Node 16.14.0)\nfunction lastUserMessage(messages: MessageData[]) {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === 'user') {\n return messages[i];\n }\n }\n return undefined;\n}\n\n/**\n * Provide a simulated system prompt for models that don't support it natively.\n */\nexport function simulateSystemPrompt(options?: {\n preface: string;\n acknowledgement: string;\n}): ModelMiddleware {\n const preface = options?.preface || 'SYSTEM INSTRUCTIONS:\\n';\n const acknowledgement = options?.acknowledgement || 'Understood.';\n\n return (req, next) => {\n const messages = [...req.messages];\n for (let i = 0; i < messages.length; i++) {\n if (req.messages[i].role === 'system') {\n const systemPrompt = messages[i].content;\n messages.splice(\n i,\n 1,\n { role: 'user', content: [{ text: preface }, ...systemPrompt] },\n { role: 'model', content: [{ text: acknowledgement }] }\n );\n break;\n }\n }\n return next({ ...req, messages });\n };\n}\n\nexport interface AugmentWithContextOptions {\n /** Preceding text to place before the rendered context documents. */\n preface?: string | null;\n /** A function to render a document into a text part to be included in the message. */\n itemTemplate?: (d: Document, options?: AugmentWithContextOptions) => string;\n /** The metadata key to use for citation reference. Pass `null` to provide no citations. */\n citationKey?: string | null;\n}\n\nexport const CONTEXT_PREFACE =\n '\\n\\nUse the following information to complete your task:\\n\\n';\nconst CONTEXT_ITEM_TEMPLATE = (\n d: Document,\n index: number,\n options?: AugmentWithContextOptions\n) => {\n let out = '- ';\n if (options?.citationKey) {\n out += `[${d.metadata![options.citationKey]}]: `;\n } else if (options?.citationKey === undefined) {\n out += `[${d.metadata?.['ref'] || d.metadata?.['id'] || index}]: `;\n }\n out += d.text + '\\n';\n return out;\n};\n\nexport function augmentWithContext(\n options?: AugmentWithContextOptions\n): ModelMiddleware {\n const preface =\n typeof options?.preface === 'undefined' ? CONTEXT_PREFACE : options.preface;\n const itemTemplate = options?.itemTemplate || CONTEXT_ITEM_TEMPLATE;\n return (req, next) => {\n // if there is no context in the request, no-op\n if (!req.docs?.length) return next(req);\n const userMessage = lastUserMessage(req.messages);\n // if there are no messages, no-op\n if (!userMessage) return next(req);\n // if there is already a context part, no-op\n const contextPartIndex = userMessage?.content.findIndex(\n (p) => p.metadata?.purpose === 'context'\n );\n const contextPart =\n contextPartIndex >= 0 && userMessage.content[contextPartIndex];\n\n if (contextPart && !contextPart.metadata?.pending) {\n return next(req);\n }\n let out = `${preface || ''}`;\n req.docs?.forEach((d, i) => {\n out += itemTemplate(new Document(d), i, options);\n });\n out += '\\n';\n if (contextPartIndex >= 0) {\n userMessage.content[contextPartIndex] = {\n ...contextPart,\n text: out,\n metadata: { purpose: 'context' },\n } as Part;\n } else {\n userMessage.content.push({ text: out, metadata: { purpose: 'context' } });\n }\n\n return next(req);\n };\n}\n\nexport interface SimulatedConstrainedGenerationOptions {\n instructionsRenderer?: (schema: Record<string, any>) => string;\n}\n\nconst DEFAULT_CONSTRAINED_GENERATION_INSTRUSCTIONS = (\n schema: Record<string, any>\n) => `Output should be in JSON format and conform to the following schema:\n\n\\`\\`\\`\n${JSON.stringify(schema)}\n\\`\\`\\`\n`;\n\n/**\n * Model middleware that simulates constrained generation by injecting generation\n * instructions into the user message.\n */\nexport function simulateConstrainedGeneration(\n options?: SimulatedConstrainedGenerationOptions\n): ModelMiddleware {\n return (req, next) => {\n let instructions: string | undefined;\n if (req.output?.constrained && req.output?.schema) {\n instructions = (\n options?.instructionsRenderer ??\n DEFAULT_CONSTRAINED_GENERATION_INSTRUSCTIONS\n )(req.output?.schema);\n\n req = {\n ...req,\n messages: injectInstructions(req.messages, instructions),\n output: {\n ...req.output,\n // we're simulating it, so to the underlying model it's unconstrained.\n constrained: false,\n format: undefined,\n contentType: undefined,\n schema: undefined,\n },\n };\n }\n\n return next(req);\n };\n}\n"],"mappings":"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","names":[]}