UNPKG

@genkit-ai/vertexai

Version:

Genkit AI framework plugin for Google Cloud Vertex AI APIs including Gemini APIs, Imagen, and more.

431 lines 11.8 kB
import { AnthropicVertex } from "@anthropic-ai/vertex-sdk"; import { z } from "genkit"; import { GenerationCommonConfigSchema, getBasicUsageStats, modelRef } from "genkit/model"; import { model as pluginModel } from "genkit/plugin"; import { getGenkitClientHeader } from "../../common/index.mjs"; import { checkModelName } from "./utils.mjs"; const ThinkingConfigSchema = z.object({ enabled: z.boolean().optional(), budgetTokens: z.number().min(1024).optional(), adaptive: z.boolean().optional(), display: z.enum(["summarized", "omitted"]).optional() }).passthrough().superRefine((value, ctx) => { if (value.enabled && value.adaptive) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["adaptive"], message: "Cannot use both enabled and adaptive thinking modes simultaneously" }); } if (value.enabled) { if (value.budgetTokens === void 0) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["budgetTokens"], message: "budgetTokens is required when thinking is enabled" }); } else if (!Number.isInteger(value.budgetTokens)) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["budgetTokens"], message: "budgetTokens must be an integer" }); } } }); const AnthropicConfigSchema = GenerationCommonConfigSchema.extend({ location: z.string().optional(), thinking: ThinkingConfigSchema.optional().describe( "The thinking configuration to use for the request. Thinking is a feature that allows the model to think about the request and provide a better response." ), output_config: z.object({ effort: z.enum(["low", "medium", "high", "xhigh"]).optional() }).passthrough().describe( "Configuration for output generation, such as setting the effort parameter." ).optional() }).passthrough(); function commonRef(name, info, configSchema = AnthropicConfigSchema) { return modelRef({ name: `vertex-model-garden/${name}`, configSchema, info: info ?? { supports: { multiturn: true, media: true, tools: true, systemRole: true, output: ["text"] } } }); } const GENERIC_MODEL = commonRef("anthropic"); const KNOWN_MODELS = { "claude-opus-4-7": commonRef("claude-opus-4-7"), "claude-sonnet-4-6": commonRef("claude-sonnet-4-6"), "claude-opus-4-6": commonRef("claude-opus-4-6"), "claude-haiku-4-5@20251001": commonRef("claude-haiku-4-5@20251001"), "claude-sonnet-4-5@20250929": commonRef("claude-sonnet-4-5@20250929"), "claude-sonnet-4@20250514": commonRef("claude-sonnet-4@20250514"), "claude-opus-4-5@20251101": commonRef("claude-opus-4-5@20251101"), "claude-opus-4-1@20250805": commonRef("claude-opus-4-1@20250805"), "claude-opus-4@20250514": commonRef("claude-opus-4@20250514") }; function isAnthropicModelName(value) { return !!value?.startsWith("claude-"); } function model(version, options = {}) { const name = checkModelName(version); return modelRef({ name: `vertex-model-garden/${name}`, config: options, configSchema: AnthropicConfigSchema, info: { ...GENERIC_MODEL.info } }); } function listActions(clientOptions) { return []; } function listKnownModels(clientOptions, pluginOptions) { return Object.keys(KNOWN_MODELS).map( (name) => defineModel(name, clientOptions, pluginOptions) ); } function defineModel(name, clientOptions, pluginOptions) { const clients = {}; const clientFactory = (region) => { if (!clients[region]) { clients[region] = new AnthropicVertex({ region, projectId: clientOptions.projectId, defaultHeaders: { "X-Goog-Api-Client": getGenkitClientHeader() } }); } return clients[region]; }; const ref = model(name); return pluginModel( { name: ref.name, ...ref.info, configSchema: ref.configSchema }, async (request, { streamingRequested, sendChunk }) => { const client = clientFactory( request.config?.location || clientOptions.location ); const modelVersion = checkModelName(ref.name); const anthropicRequest = toAnthropicRequest(modelVersion, request); if (!streamingRequested) { const response = await client.messages.create({ ...anthropicRequest, stream: false }); return fromAnthropicResponse(request, response); } else { const stream = await client.messages.stream(anthropicRequest); for await (const event of stream) { if (event.type === "content_block_delta") { sendChunk({ index: 0, content: [ { text: event.delta.text } ] }); } } return fromAnthropicResponse(request, await stream.finalMessage()); } } ); } function toAnthropicRequest(model2, input) { let system = void 0; const messages = []; for (const msg of input.messages) { if (msg.role === "system") { system = msg.content.map((c) => { if (!c.text) { throw new Error( "Only text context is supported for system messages." ); } return c.text; }).join(); } else if (msg.content[msg.content.length - 1].toolResponse) { messages.push({ role: "user", content: toAnthropicContent(msg.content) }); } else { messages.push({ role: toAnthropicRole(msg.role), content: toAnthropicContent(msg.content) }); } } const { location, version, maxOutputTokens, stopSequences, temperature, topK, topP, thinking, output_config, ...restConfig } = input.config ?? {}; const request = { model: model2, messages, // https://docs.anthropic.com/claude/docs/models-overview#model-comparison max_tokens: maxOutputTokens ?? 4096, ...restConfig }; if (system) { request["system"] = system; } if (input.tools) { request.tools = input.tools?.map((tool) => { return { name: tool.name, description: tool.description, input_schema: tool.inputSchema }; }); } if (stopSequences) { request.stop_sequences = stopSequences; } if (temperature !== void 0) { request.temperature = temperature; } if (topK !== void 0) { request.top_k = topK; } if (topP !== void 0) { request.top_p = topP; } if (thinking) { const anthropicThinking = toAnthropicThinking(thinking); if (anthropicThinking) { request.thinking = anthropicThinking; } } if (output_config) { request.output_config = output_config; } return request; } function toAnthropicThinking(config) { if (!config) return void 0; const { enabled, budgetTokens, adaptive, display } = config; if (adaptive === true) { return { type: "adaptive", ...display !== void 0 && { display } }; } if (enabled === true) { if (budgetTokens === void 0) { throw new Error("budgetTokens is required when thinking is enabled"); } return { type: "enabled", budget_tokens: budgetTokens }; } if (enabled === false) { return { type: "disabled" }; } if (budgetTokens !== void 0) { return { type: "enabled", budget_tokens: budgetTokens }; } return void 0; } function toAnthropicContent(content) { return content.map((p) => { if (p.reasoning) { const signature = p.metadata?.thoughtSignature; return { type: "thinking", thinking: p.reasoning, ...signature ? { signature } : {} }; } if (p.text) { return { type: "text", text: p.text }; } if (p.media) { let b64Data = p.media.url; if (b64Data.startsWith("data:")) { b64Data = b64Data.substring(b64Data.indexOf(",") + 1); } return { type: "image", source: { type: "base64", data: b64Data, media_type: p.media.contentType } }; } if (p.toolRequest) { return toAnthropicToolRequest(p.toolRequest); } if (p.toolResponse) { return toAnthropicToolResponse(p); } throw new Error(`Unsupported content type: ${JSON.stringify(p)}`); }); } function toAnthropicRole(role) { if (role === "model") { return "assistant"; } if (role === "user") { return "user"; } if (role === "tool") { return "assistant"; } throw new Error(`Unsupported role type ${role}`); } function fromAnthropicTextPart(part) { return { text: part.text }; } function fromAnthropicToolCallPart(part) { return { toolRequest: { name: part.name, input: part.input, ref: part.id } }; } function fromAnthropicThinkingPart(part) { if (part.signature !== void 0) { return { reasoning: part.thinking, metadata: { thoughtSignature: part.signature } }; } return { reasoning: part.thinking }; } function fromAnthropicPart(part) { if (part.type === "text") return fromAnthropicTextPart(part); if (part.type === "tool_use") return fromAnthropicToolCallPart(part); if (part.type === "thinking") return fromAnthropicThinkingPart(part); const unknownType = part.type; console.warn( `Unexpected Anthropic content block type: ${unknownType}. Returning empty text.` ); return { text: "" }; } function fromAnthropicResponse(input, response) { const parts = response.content; const message = { role: "model", content: parts.map(fromAnthropicPart) }; return { message, finishReason: toGenkitFinishReason( response.stop_reason ), custom: { id: response.id, model: response.model, type: response.type }, raw: response, usage: { ...getBasicUsageStats(input.messages, message), inputTokens: response.usage.input_tokens, outputTokens: response.usage.output_tokens, custom: { cache_creation_input_tokens: response.usage.cache_creation_input_tokens ?? 0, cache_read_input_tokens: response.usage.cache_read_input_tokens ?? 0, ephemeral_5m_input_tokens: response.usage.cache_creation?.ephemeral_5m_input_tokens ?? 0, ephemeral_1h_input_tokens: response.usage.cache_creation?.ephemeral_1h_input_tokens ?? 0 } } }; } function toGenkitFinishReason(reason) { switch (reason) { case "end_turn": return "stop"; case "max_tokens": return "length"; case "stop_sequence": return "stop"; case "tool_use": return "stop"; case null: return "unknown"; default: return "other"; } } function toAnthropicToolRequest(tool) { if (!tool.name) { throw new Error("Tool name is required"); } if (!/^[a-zA-Z0-9_-]{1,64}$/.test(tool.name)) { throw new Error( `Tool name ${tool.name} contains invalid characters. Only letters, numbers, and underscores are allowed, and the name must be between 1 and 64 characters long.` ); } const declaration = { type: "tool_use", id: tool.ref, name: tool.name, input: tool.input }; return declaration; } function toAnthropicToolResponse(part) { if (!part.toolResponse?.ref) { throw new Error("Tool response reference is required"); } if (!part.toolResponse.output) { throw new Error("Tool response output is required"); } return { type: "tool_result", tool_use_id: part.toolResponse.ref, content: JSON.stringify(part.toolResponse.output) }; } export { AnthropicConfigSchema, GENERIC_MODEL, KNOWN_MODELS, ThinkingConfigSchema, defineModel, fromAnthropicResponse, isAnthropicModelName, listActions, listKnownModels, model, toAnthropicRequest }; //# sourceMappingURL=anthropic.mjs.map