UNPKG

@genkit-ai/anthropic

Version:

Genkit AI framework plugin for Anthropic APIs.

325 lines 9.96 kB
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