@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
JavaScript
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