@genkit-ai/ai
Version:
Genkit AI framework generative AI APIs.
331 lines • 9.76 kB
JavaScript
import {
assertUnstable,
GenkitError,
isAction,
runWithContext,
sentinelNoopStreamingCallback
} from "@genkit-ai/core";
import { Channel } from "@genkit-ai/core/async";
import { Registry } from "@genkit-ai/core/registry";
import { toJsonSchema } from "@genkit-ai/core/schema";
import {
injectInstructions,
resolveFormat,
resolveInstructions
} from "./formats/index.js";
import {
generateHelper,
shouldInjectFormatInstructions
} from "./generate/action.js";
import { GenerateResponseChunk } from "./generate/chunk.js";
import { GenerateResponse } from "./generate/response.js";
import { Message } from "./message.js";
import {
resolveModel
} from "./model.js";
import { isExecutablePrompt } from "./prompt.js";
import { isDynamicResourceAction } from "./resource.js";
import {
isDynamicTool,
resolveTools,
toToolDefinition
} from "./tool.js";
async function toGenerateRequest(registry, options) {
const messages = [];
if (options.system) {
messages.push({
role: "system",
content: Message.parseContent(options.system)
});
}
if (options.messages) {
messages.push(...options.messages.map((m) => Message.parseData(m)));
}
if (options.prompt) {
messages.push({
role: "user",
content: Message.parseContent(options.prompt)
});
}
if (messages.length === 0) {
throw new GenkitError({
status: "INVALID_ARGUMENT",
message: "at least one message is required in generate request"
});
}
if (options.resume && !(messages.at(-1)?.role === "model" && messages.at(-1)?.content.find((p) => !!p.toolRequest))) {
throw new GenkitError({
status: "FAILED_PRECONDITION",
message: `Last message must be a 'model' role with at least one tool request to 'resume' generation.`,
detail: messages.at(-1)
});
}
let tools;
if (options.tools) {
tools = await resolveTools(registry, options.tools);
}
const resolvedSchema = toJsonSchema({
schema: options.output?.schema,
jsonSchema: options.output?.jsonSchema
});
const resolvedFormat = await resolveFormat(registry, options.output);
const instructions = resolveInstructions(
resolvedFormat,
resolvedSchema,
options?.output?.instructions
);
const out = {
messages: shouldInjectFormatInstructions(
resolvedFormat?.config,
options.output
) ? injectInstructions(messages, instructions) : messages,
config: options.config,
docs: options.docs,
tools: tools?.map(toToolDefinition) || [],
output: {
...resolvedFormat?.config || {},
...options.output,
schema: resolvedSchema
}
};
if (!out?.output?.schema) delete out?.output?.schema;
return out;
}
class GenerationResponseError extends GenkitError {
detail;
constructor(response, message, status, detail) {
super({
status: status || "FAILED_PRECONDITION",
message
});
this.detail = { response, ...detail };
}
}
async function toolsToActionRefs(registry, toolOpt) {
if (!toolOpt) return;
const tools = [];
for (const t of toolOpt) {
if (typeof t === "string") {
tools.push(await resolveFullToolName(registry, t));
} else if (isAction(t) || isDynamicTool(t)) {
tools.push(`/${t.__action.metadata?.type}/${t.__action.name}`);
} else if (isExecutablePrompt(t)) {
const promptToolAction = await t.asTool();
tools.push(`/prompt/${promptToolAction.__action.name}`);
} else {
throw new Error(`Unable to determine type of tool: ${JSON.stringify(t)}`);
}
}
return tools;
}
function messagesFromOptions(options) {
const messages = [];
if (options.system) {
messages.push({
role: "system",
content: Message.parseContent(options.system)
});
}
if (options.messages) {
messages.push(...options.messages);
}
if (options.prompt) {
messages.push({
role: "user",
content: Message.parseContent(options.prompt)
});
}
if (messages.length === 0) {
throw new GenkitError({
status: "INVALID_ARGUMENT",
message: "at least one message is required in generate request"
});
}
return messages;
}
class GenerationBlockedError extends GenerationResponseError {
}
async function generate(registry, options) {
const resolvedOptions = {
...await Promise.resolve(options)
};
const resolvedFormat = await resolveFormat(registry, resolvedOptions.output);
registry = maybeRegisterDynamicTools(registry, resolvedOptions);
registry = maybeRegisterDynamicResources(registry, resolvedOptions);
const params = await toGenerateActionOptions(registry, resolvedOptions);
const tools = await toolsToActionRefs(registry, resolvedOptions.tools);
const streamingCallback = stripNoop(
resolvedOptions.onChunk ?? resolvedOptions.streamingCallback
);
const response = await runWithContext(
resolvedOptions.context,
() => generateHelper(registry, {
rawRequest: params,
middleware: resolvedOptions.use,
abortSignal: resolvedOptions.abortSignal,
streamingCallback
})
);
const request = await toGenerateRequest(registry, {
...resolvedOptions,
tools
});
return new GenerateResponse(response, {
request: response.request ?? request,
parser: resolvedFormat?.handler(request.output?.schema).parseMessage
});
}
async function generateOperation(registry, options) {
assertUnstable(registry, "beta", "generateOperation is a beta feature.");
options = await options;
const resolvedModel = await resolveModel(registry, options.model);
if (!resolvedModel.modelAction.__action.metadata?.model.supports?.longRunning) {
throw new GenkitError({
status: "INVALID_ARGUMENT",
message: `Model '${resolvedModel.modelAction.__action.name}' does not support long running operations.`
});
}
const { operation } = await generate(registry, options);
if (!operation) {
throw new GenkitError({
status: "FAILED_PRECONDITION",
message: `Model '${resolvedModel.modelAction.__action.name}' did not return an operation.`
});
}
return operation;
}
function maybeRegisterDynamicTools(registry, options) {
let hasDynamicTools = false;
options?.tools?.forEach((t) => {
if (isDynamicTool(t)) {
if (!hasDynamicTools) {
hasDynamicTools = true;
registry = Registry.withParent(registry);
}
registry.registerAction("tool", t);
}
});
return registry;
}
function maybeRegisterDynamicResources(registry, options) {
let hasDynamicResources = false;
options?.resources?.forEach((r) => {
if (isDynamicResourceAction(r)) {
if (!hasDynamicResources) {
hasDynamicResources = true;
registry = Registry.withParent(registry);
}
registry.registerAction("resource", r);
}
});
return registry;
}
async function toGenerateActionOptions(registry, options) {
const resolvedModel = await resolveModel(registry, options.model);
const tools = await toolsToActionRefs(registry, options.tools);
const messages = messagesFromOptions(options);
const resolvedSchema = toJsonSchema({
schema: options.output?.schema,
jsonSchema: options.output?.jsonSchema
});
if ((options.output?.schema || options.output?.jsonSchema) && !options.output?.format) {
options.output.format = "json";
}
const params = {
model: resolvedModel.modelAction.__action.name,
docs: options.docs,
messages,
tools,
toolChoice: options.toolChoice,
config: {
version: resolvedModel.version,
...stripUndefinedOptions(resolvedModel.config),
...stripUndefinedOptions(options.config)
},
output: options.output && {
...options.output,
format: options.output.format,
jsonSchema: resolvedSchema
},
// coerce reply and restart into arrays for the action schema
resume: options.resume && {
respond: [options.resume.respond || []].flat(),
restart: [options.resume.restart || []].flat(),
metadata: options.resume.metadata
},
returnToolRequests: options.returnToolRequests,
maxTurns: options.maxTurns,
stepName: options.stepName
};
if (Object.keys(params.config).length === 0 && !options.config) {
delete params.config;
}
return params;
}
function stripNoop(callback) {
if (callback === sentinelNoopStreamingCallback) {
return void 0;
}
return callback;
}
function stripUndefinedOptions(input) {
if (!input) return input;
const copy = { ...input };
Object.keys(input).forEach((key) => {
if (copy[key] === void 0) {
delete copy[key];
}
});
return copy;
}
async function resolveFullToolName(registry, name) {
if (await registry.lookupAction(`/tool/${name}`)) {
return `/tool/${name}`;
} else if (await registry.lookupAction(`/prompt/${name}`)) {
return `/prompt/${name}`;
} else {
throw new Error(`Unable to determine type of of tool: ${name}`);
}
}
function generateStream(registry, options) {
const channel = new Channel();
const generated = Promise.resolve(options).then(
(resolvedOptions) => generate(registry, {
...resolvedOptions,
onChunk: (chunk) => channel.send(chunk)
})
);
generated.then(
() => channel.close(),
(err) => channel.error(err)
);
return {
response: generated,
stream: channel
};
}
function tagAsPreamble(msgs) {
if (!msgs) {
return void 0;
}
return msgs.map((m) => ({
...m,
metadata: {
...m.metadata,
preamble: true
}
}));
}
export {
GenerateResponse,
GenerateResponseChunk,
GenerationBlockedError,
GenerationResponseError,
generate,
generateOperation,
generateStream,
tagAsPreamble,
toGenerateActionOptions,
toGenerateRequest
};
//# sourceMappingURL=generate.mjs.map