@genkit-ai/anthropic
Version:
Genkit AI framework plugin for Anthropic APIs.
325 lines • 9.96 kB
JavaScript
import { logger } from "genkit/logging";
import { checkModelName, removeUndefinedProperties } from "../utils.mjs";
import { BaseRunner } from "./base.mjs";
import {
citationsDeltaToPart,
redactedThinkingBlockToPart,
textBlockToPart,
textDeltaToPart,
thinkingBlockToPart,
thinkingDeltaToPart,
toolUseBlockToPart,
webSearchToolResultBlockToPart
} from "./converters/shared.mjs";
import {
serverToolUseBlockToPart,
toDocumentBlock
} from "./converters/stable.mjs";
class Runner extends BaseRunner {
constructor(params) {
super(params);
}
toAnthropicMessageContent(part) {
if (part.reasoning) {
const signature = this.getThinkingSignature(part);
if (!signature) {
throw new Error(
"Anthropic thinking parts require a signature when sending back to the API. Preserve the `metadata.thoughtSignature` value from the original response."
);
}
return {
type: "thinking",
thinking: part.reasoning,
signature
};
}
const redactedThinking = this.getRedactedThinkingData(part);
if (redactedThinking !== void 0) {
return {
type: "redacted_thinking",
data: redactedThinking
};
}
if (part.text) {
return {
type: "text",
text: part.text,
citations: null,
// This is intentional. `part.metadata?.cache_control` is unknown, and casting it to the relevant type of the property makes it more robust to Anthropic SDK API changes.
cache_control: part.metadata?.cache_control
};
}
if (part.custom?.anthropicDocument) {
return toDocumentBlock(
part.custom.anthropicDocument
);
}
if (part.media) {
if (part.media.contentType === "application/pdf") {
return {
type: "document",
source: this.toPdfDocumentSource(part.media),
cache_control: part.metadata?.cache_control
};
}
const source = this.toImageSource(part.media);
if (source.kind === "base64") {
return {
type: "image",
source: {
type: "base64",
data: source.data,
media_type: source.mediaType
},
cache_control: part.metadata?.cache_control
};
}
return {
type: "image",
source: {
type: "url",
url: source.url
},
cache_control: part.metadata?.cache_control
};
}
if (part.toolRequest) {
if (!part.toolRequest.ref) {
throw new Error(
`Tool request ref is required for Anthropic API. Part: ${JSON.stringify(
part.toolRequest
)}`
);
}
return {
type: "tool_use",
id: part.toolRequest.ref,
name: part.toolRequest.name,
input: part.toolRequest.input,
cache_control: part.metadata?.cache_control
};
}
if (part.toolResponse) {
if (!part.toolResponse.ref) {
throw new Error(
`Tool response ref is required for Anthropic API. Part: ${JSON.stringify(
part.toolResponse
)}`
);
}
return {
type: "tool_result",
tool_use_id: part.toolResponse.ref,
content: [this.toAnthropicToolResponseContent(part)],
cache_control: part.metadata?.cache_control
};
}
throw new Error(
`Unsupported genkit part fields encountered for current message role: ${JSON.stringify(
part
)}.`
);
}
toAnthropicRequestBody(modelName, request) {
if (request.output?.format && request.output.format !== "text") {
throw new Error(
`Only text output format is supported for Claude models currently`
);
}
const { system, messages } = this.toAnthropicMessages(request.messages);
const mappedModelName = request.config?.version ?? checkModelName(modelName);
const thinkingConfig = this.toAnthropicThinkingConfig(
request.config?.thinking
);
const {
topP,
topK,
apiVersion: _1,
thinking: _2,
maxOutputTokens,
stopSequences,
version,
apiKey,
...restConfig
} = request.config ?? {};
const body = {
model: mappedModelName,
max_tokens: maxOutputTokens ?? this.DEFAULT_MAX_OUTPUT_TOKENS,
messages,
system,
stop_sequences: stopSequences,
temperature: request.config?.temperature,
top_k: topK,
top_p: topP,
tool_choice: request.config?.tool_choice,
metadata: request.config?.metadata,
tools: request.tools?.map((tool) => this.toAnthropicTool(tool)),
thinking: thinkingConfig,
...restConfig
};
return removeUndefinedProperties(body);
}
toAnthropicStreamingRequestBody(modelName, request) {
if (request.output?.format && request.output.format !== "text") {
throw new Error(
`Only text output format is supported for Claude models currently`
);
}
const { system, messages } = this.toAnthropicMessages(request.messages);
const mappedModelName = request.config?.version ?? checkModelName(modelName);
const thinkingConfig = this.toAnthropicThinkingConfig(
request.config?.thinking
);
const {
topP,
topK,
apiVersion: _1,
thinking: _2,
maxOutputTokens,
stopSequences,
version,
apiKey,
...restConfig
} = request.config ?? {};
const body = {
model: mappedModelName,
max_tokens: request.config?.maxOutputTokens ?? this.DEFAULT_MAX_OUTPUT_TOKENS,
messages,
stream: true,
system,
stop_sequences: request.config?.stopSequences,
temperature: request.config?.temperature,
top_k: topK,
top_p: topP,
tool_choice: request.config?.tool_choice,
metadata: request.config?.metadata,
tools: request.tools?.map((tool) => this.toAnthropicTool(tool)),
thinking: thinkingConfig,
...restConfig
};
return removeUndefinedProperties(body);
}
async createMessage(body, abortSignal) {
return await this.client.messages.create(body, { signal: abortSignal });
}
streamMessages(body, abortSignal) {
return this.client.messages.stream(body, { signal: abortSignal });
}
toGenkitResponse(message) {
return this.fromAnthropicResponse(message);
}
toGenkitPart(event) {
return this.fromAnthropicContentBlockChunk(event);
}
fromAnthropicContentBlockChunk(event) {
if (event.type === "content_block_delta") {
const delta = event.delta;
if (delta.type === "text_delta") {
return textDeltaToPart(delta);
}
if (delta.type === "thinking_delta") {
return thinkingDeltaToPart(delta);
}
if (delta.type === "citations_delta") {
return citationsDeltaToPart(delta);
}
return void 0;
}
if (event.type === "content_block_start") {
const block = event.content_block;
switch (block.type) {
case "text":
return textBlockToPart(block);
case "tool_use":
return toolUseBlockToPart(block);
case "thinking":
return thinkingBlockToPart(block);
case "redacted_thinking":
return redactedThinkingBlockToPart(block);
case "server_tool_use":
return serverToolUseBlockToPart(block);
case "web_search_tool_result":
return webSearchToolResultBlockToPart(block);
default: {
const unknownType = block.type;
logger.warn(
`Unexpected Anthropic content block type in stream: ${unknownType}. Returning undefined. Content block: ${JSON.stringify(block)}`
);
return void 0;
}
}
}
return void 0;
}
fromAnthropicContentBlock(contentBlock) {
switch (contentBlock.type) {
case "text":
return textBlockToPart(contentBlock);
case "tool_use":
return toolUseBlockToPart(contentBlock);
case "thinking":
return thinkingBlockToPart(contentBlock);
case "redacted_thinking":
return redactedThinkingBlockToPart(contentBlock);
case "server_tool_use":
return serverToolUseBlockToPart(contentBlock);
case "web_search_tool_result":
return webSearchToolResultBlockToPart(contentBlock);
default: {
const unknownType = contentBlock.type;
logger.warn(
`Unexpected Anthropic content block type: ${unknownType}. Returning empty text. Content block: ${JSON.stringify(contentBlock)}`
);
return { text: "" };
}
}
}
fromAnthropicStopReason(reason) {
switch (reason) {
case "max_tokens":
return "length";
case "end_turn":
// fall through
case "stop_sequence":
// fall through
case "tool_use":
return "stop";
case null:
return "unknown";
default:
return "other";
}
}
fromAnthropicResponse(response) {
return {
candidates: [
{
index: 0,
finishReason: this.fromAnthropicStopReason(response.stop_reason),
message: {
role: "model",
content: response.content.map(
(block) => this.fromAnthropicContentBlock(block)
)
}
}
],
usage: {
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
}
},
custom: response,
raw: response
};
}
}
export {
Runner
};
//# sourceMappingURL=stable.mjs.map