From a0f3a6de76565b2dcf1506956fb89ed8c3b79661 Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Mon, 8 Dec 2025 13:23:42 +0000 Subject: [PATCH 1/6] refactor(anthropic): body request simplified and allowed additional params --- js/plugins/anthropic/src/runner/beta.ts | 123 ++++++++++------------ js/plugins/anthropic/src/runner/stable.ts | 107 ++++++++----------- js/plugins/anthropic/src/types.ts | 2 +- 3 files changed, 98 insertions(+), 134 deletions(-) diff --git a/js/plugins/anthropic/src/runner/beta.ts b/js/plugins/anthropic/src/runner/beta.ts index 9fd7a306a1..86451317a8 100644 --- a/js/plugins/anthropic/src/runner/beta.ts +++ b/js/plugins/anthropic/src/runner/beta.ts @@ -299,47 +299,41 @@ export class BetaRunner extends BaseRunner { : system; } + const thinkingConfig = this.toAnthropicThinkingConfig( + request.config?.thinking + ) as BetaMessageCreateParams['thinking'] | undefined; + + const { thinking: defaultThinkingConfig, ...restConfig } = + request.config || {}; + const body: BetaMessageCreateParamsNonStreaming = { model: mappedModelName, max_tokens: request.config?.maxOutputTokens ?? this.DEFAULT_MAX_OUTPUT_TOKENS, messages, - betas: BETA_APIS, + system: betaSystem, + stop_sequences: request.config?.stopSequences, + temperature: request.config?.temperature, + top_k: request.config?.topK, + top_p: request.config?.topP, + tool_choice: request.config?.tool_choice, + metadata: request.config?.metadata, + tools: request.tools?.map((tool) => this.toAnthropicTool(tool)), + thinking: + (defaultThinkingConfig as BetaMessageCreateParams['thinking']) ?? + thinkingConfig, + output_format: this.isStructuredOutputEnabled(request) + ? { + type: 'json_schema', + schema: toAnthropicSchema(request.output!.schema!), + } + : undefined, + betas: Array.isArray(request.config?.betas) + ? [...BETA_APIS, ...(request.config?.betas ?? [])] + : [...BETA_APIS], + ...restConfig, }; - if (betaSystem !== undefined) body.system = betaSystem; - if (request.config?.stopSequences !== undefined) - body.stop_sequences = request.config.stopSequences; - if (request.config?.temperature !== undefined) - body.temperature = request.config.temperature; - if (request.config?.topK !== undefined) body.top_k = request.config.topK; - if (request.config?.topP !== undefined) body.top_p = request.config.topP; - if (request.config?.tool_choice !== undefined) { - body.tool_choice = request.config - .tool_choice as BetaMessageCreateParams['tool_choice']; - } - if (request.config?.metadata !== undefined) { - body.metadata = request.config - .metadata as BetaMessageCreateParams['metadata']; - } - if (request.tools) { - body.tools = request.tools.map((tool) => this.toAnthropicTool(tool)); - } - const thinkingConfig = this.toAnthropicThinkingConfig( - request.config?.thinking - ); - if (thinkingConfig) { - body.thinking = thinkingConfig as BetaMessageCreateParams['thinking']; - } - - // Apply structured output when model supports it and constrained output is requested - if (this.isStructuredOutputEnabled(request)) { - body.output_format = { - type: 'json_schema', - schema: toAnthropicSchema(request.output!.schema!), - }; - } - return body; } @@ -369,47 +363,42 @@ export class BetaRunner extends BaseRunner { ] : system; + const thinkingConfig = this.toAnthropicThinkingConfig( + request.config?.thinking + ) as BetaMessageCreateParams['thinking'] | undefined; + + const { thinking: defaultThinkingConfig, ...restConfig } = + request.config || {}; + const body: BetaMessageCreateParamsStreaming = { model: mappedModelName, max_tokens: request.config?.maxOutputTokens ?? this.DEFAULT_MAX_OUTPUT_TOKENS, messages, stream: true, - betas: BETA_APIS, + system: betaSystem, + stop_sequences: request.config?.stopSequences, + temperature: request.config?.temperature, + top_k: request.config?.topK, + top_p: request.config?.topP, + tool_choice: request.config?.tool_choice, + metadata: request.config?.metadata, + tools: request.tools?.map((tool) => this.toAnthropicTool(tool)), + thinking: + (defaultThinkingConfig as BetaMessageCreateParams['thinking']) ?? + thinkingConfig, + output_format: this.isStructuredOutputEnabled(request) + ? { + type: 'json_schema', + schema: toAnthropicSchema(request.output!.schema!), + } + : undefined, + betas: Array.isArray(request.config?.betas) + ? [...BETA_APIS, ...(request.config?.betas ?? [])] + : [...BETA_APIS], + ...restConfig, }; - if (betaSystem !== undefined) body.system = betaSystem; - if (request.config?.stopSequences !== undefined) - body.stop_sequences = request.config.stopSequences; - if (request.config?.temperature !== undefined) - body.temperature = request.config.temperature; - if (request.config?.topK !== undefined) body.top_k = request.config.topK; - if (request.config?.topP !== undefined) body.top_p = request.config.topP; - if (request.config?.tool_choice !== undefined) { - body.tool_choice = request.config - .tool_choice as BetaMessageCreateParams['tool_choice']; - } - if (request.config?.metadata !== undefined) { - body.metadata = request.config - .metadata as BetaMessageCreateParams['metadata']; - } - if (request.tools) { - body.tools = request.tools.map((tool) => this.toAnthropicTool(tool)); - } - const thinkingConfig = this.toAnthropicThinkingConfig( - request.config?.thinking - ); - if (thinkingConfig) { - body.thinking = thinkingConfig as BetaMessageCreateParams['thinking']; - } - - // Apply structured output when model supports it and constrained output is requested - if (this.isStructuredOutputEnabled(request)) { - body.output_format = { - type: 'json_schema', - schema: toAnthropicSchema(request.output!.schema!), - }; - } return body; } diff --git a/js/plugins/anthropic/src/runner/stable.ts b/js/plugins/anthropic/src/runner/stable.ts index 0c8f7ffc4f..08fda1ee00 100644 --- a/js/plugins/anthropic/src/runner/stable.ts +++ b/js/plugins/anthropic/src/runner/stable.ts @@ -16,11 +16,11 @@ import { MessageStream } from '@anthropic-ai/sdk/lib/MessageStream.js'; import type { + MessageCreateParams as BetaMessageCreateParams, ContentBlock, DocumentBlockParam, ImageBlockParam, Message, - MessageCreateParams, MessageCreateParamsNonStreaming, MessageCreateParamsStreaming, MessageParam, @@ -197,50 +197,38 @@ export class Runner extends BaseRunner { ] : system; + const thinkingConfig = this.toAnthropicThinkingConfig( + request.config?.thinking + ) as BetaMessageCreateParams['thinking'] | undefined; + + const { thinking: defaultThinkingConfig, ...restConfig } = + request.config || {}; + const body: MessageCreateParamsNonStreaming = { model: mappedModelName, max_tokens: request.config?.maxOutputTokens ?? this.DEFAULT_MAX_OUTPUT_TOKENS, messages, + system: systemValue, + stop_sequences: request.config?.stopSequences, + temperature: request.config?.temperature, + top_k: request.config?.topK, + top_p: request.config?.topP, + tool_choice: request.config?.tool_choice, + metadata: request.config?.metadata, + tools: request.tools?.map((tool) => this.toAnthropicTool(tool)), + thinking: + (defaultThinkingConfig as BetaMessageCreateParams['thinking']) ?? + thinkingConfig, + ...restConfig, }; - if (systemValue !== undefined) { - body.system = systemValue; - } - - if (request.tools) { - body.tools = request.tools.map((tool) => this.toAnthropicTool(tool)); - } - if (request.config?.topK !== undefined) { - body.top_k = request.config.topK; - } - if (request.config?.topP !== undefined) { - body.top_p = request.config.topP; - } - if (request.config?.temperature !== undefined) { - body.temperature = request.config.temperature; - } - if (request.config?.stopSequences !== undefined) { - body.stop_sequences = request.config.stopSequences; - } - if (request.config?.metadata !== undefined) { - body.metadata = request.config.metadata; - } - if (request.config?.tool_choice !== undefined) { - body.tool_choice = request.config.tool_choice; - } - const thinkingConfig = this.toAnthropicThinkingConfig( - request.config?.thinking - ); - if (thinkingConfig) { - body.thinking = thinkingConfig as MessageCreateParams['thinking']; - } - if (request.output?.format && request.output.format !== 'text') { throw new Error( `Only text output format is supported for Claude models currently` ); } + return body; } @@ -267,52 +255,39 @@ export class Runner extends BaseRunner { ] : system; + const thinkingConfig = this.toAnthropicThinkingConfig( + request.config?.thinking + ) as BetaMessageCreateParams['thinking'] | undefined; + + const { thinking: defaultThinkingConfig, ...restConfig } = + request.config || {}; + const body: MessageCreateParamsStreaming = { model: mappedModelName, max_tokens: request.config?.maxOutputTokens ?? this.DEFAULT_MAX_OUTPUT_TOKENS, messages, stream: true, + system: systemValue, + stop_sequences: request.config?.stopSequences, + temperature: request.config?.temperature, + top_k: request.config?.topK, + top_p: request.config?.topP, + tool_choice: request.config?.tool_choice, + metadata: request.config?.metadata, + tools: request.tools?.map((tool) => this.toAnthropicTool(tool)), + thinking: + (defaultThinkingConfig as BetaMessageCreateParams['thinking']) ?? + thinkingConfig, + ...restConfig, }; - if (systemValue !== undefined) { - body.system = systemValue; - } - - if (request.tools) { - body.tools = request.tools.map((tool) => this.toAnthropicTool(tool)); - } - if (request.config?.topK !== undefined) { - body.top_k = request.config.topK; - } - if (request.config?.topP !== undefined) { - body.top_p = request.config.topP; - } - if (request.config?.temperature !== undefined) { - body.temperature = request.config.temperature; - } - if (request.config?.stopSequences !== undefined) { - body.stop_sequences = request.config.stopSequences; - } - if (request.config?.metadata !== undefined) { - body.metadata = request.config.metadata; - } - if (request.config?.tool_choice !== undefined) { - body.tool_choice = request.config.tool_choice; - } - const thinkingConfig = this.toAnthropicThinkingConfig( - request.config?.thinking - ); - if (thinkingConfig) { - body.thinking = - thinkingConfig as MessageCreateParamsStreaming['thinking']; - } - if (request.output?.format && request.output.format !== 'text') { throw new Error( `Only text output format is supported for Claude models currently` ); } + return body; } diff --git a/js/plugins/anthropic/src/types.ts b/js/plugins/anthropic/src/types.ts index 9796d71c36..0e83f15f97 100644 --- a/js/plugins/anthropic/src/types.ts +++ b/js/plugins/anthropic/src/types.ts @@ -86,7 +86,7 @@ export const AnthropicBaseConfigSchema = GenerationCommonConfigSchema.extend({ .optional(), /** Optional shorthand to pick API surface for this request. */ apiVersion: z.enum(['stable', 'beta']).optional(), -}); +}).passthrough(); export type AnthropicBaseConfigSchemaType = typeof AnthropicBaseConfigSchema; From b85f6757bd7dc33b855912a8a664f6779d45a0c1 Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Tue, 9 Dec 2025 12:33:28 +0000 Subject: [PATCH 2/6] feat(testapps/anthropic): add test app for additional params --- js/testapps/anthropic/package.json | 1 + .../anthropic/src/beta/additional_params.ts | 83 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 js/testapps/anthropic/src/beta/additional_params.ts diff --git a/js/testapps/anthropic/package.json b/js/testapps/anthropic/package.json index 08e1a0d2fd..56a2ffc613 100644 --- a/js/testapps/anthropic/package.json +++ b/js/testapps/anthropic/package.json @@ -10,6 +10,7 @@ "start:beta": "node lib/beta/basic.js", "dev:stable": "genkit start -- npx tsx --watch src/stable/basic.ts", "dev:beta": "genkit start -- npx tsx --watch src/beta/basic.ts", + "dev:beta:additional-params": "genkit start -- npx tsx --watch src/beta/additional_params.ts", "dev:stable:text-plain": "genkit start -- npx tsx --watch src/stable/text-plain.ts", "dev:stable:webp": "genkit start -- npx tsx --watch src/stable/webp.ts", "dev:stable:pdf": "genkit start -- npx tsx --watch src/stable/pdf.ts", diff --git a/js/testapps/anthropic/src/beta/additional_params.ts b/js/testapps/anthropic/src/beta/additional_params.ts new file mode 100644 index 0000000000..2e443fc01a --- /dev/null +++ b/js/testapps/anthropic/src/beta/additional_params.ts @@ -0,0 +1,83 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { anthropic } from '@genkit-ai/anthropic'; +import { genkit } from 'genkit'; + +const ai = genkit({ + plugins: [ + // Default all flows in this sample to the beta surface + anthropic({ + apiVersion: 'beta', + cacheSystemPrompt: true, + apiKey: process.env.ANTHROPIC_API_KEY, + }), + ], +}); + +const betaOpus45 = anthropic.model('claude-opus-4-5', { apiVersion: 'beta' }); + +ai.defineFlow('anthropic-beta-additional-params', async () => { + const { text } = await ai.generate({ + model: betaOpus45, + prompt: + 'You are Claude on the beta API. Provide a concise greeting that mentions that you are using the beta API.', + config: { + temperature: 0.6, + // Additional param (not directly supported by the plugin, but can be passed through to the API) + betas: ['effort-2025-11-24'], + // Additional param (not directly supported by the plugin, but can be passed through to the API) + output_config: { + effort: 'medium', + }, + }, + }); + + return text; +}); + +ai.defineFlow( + 'anthropic-beta-additional-params-stream', + async (_, { sendChunk }) => { + const { stream } = ai.generateStream({ + model: betaOpus45, + prompt: [ + { + text: 'Outline two experimental capabilities unlocked by the Anthropic beta API.', + }, + ], + config: { + temperature: 0.4, + // Additional param (not directly supported by the plugin, but can be passed through to the API) + betas: ['effort-2025-11-24'], + // Additional param (not directly supported by the plugin, but can be passed through to the API) + output_config: { + effort: 'medium', + }, + }, + }); + + const collected: string[] = []; + for await (const chunk of stream) { + if (chunk.text) { + collected.push(chunk.text); + sendChunk(chunk.text); + } + } + + return collected.join(''); + } +); From 806bb09938e1cda4c5d2e8c8252420a0d2c84c0e Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Tue, 9 Dec 2025 12:33:50 +0000 Subject: [PATCH 3/6] fix(anthropic): sending wrong properties to Anthropic --- js/plugins/anthropic/src/runner/beta.ts | 38 +++++++++++---- js/plugins/anthropic/src/runner/stable.ts | 58 +++++++++++++++-------- js/plugins/anthropic/src/types.ts | 46 +++++++++++++----- 3 files changed, 99 insertions(+), 43 deletions(-) diff --git a/js/plugins/anthropic/src/runner/beta.ts b/js/plugins/anthropic/src/runner/beta.ts index 86451317a8..42d71c1f39 100644 --- a/js/plugins/anthropic/src/runner/beta.ts +++ b/js/plugins/anthropic/src/runner/beta.ts @@ -303,8 +303,17 @@ export class BetaRunner extends BaseRunner { request.config?.thinking ) as BetaMessageCreateParams['thinking'] | undefined; - const { thinking: defaultThinkingConfig, ...restConfig } = - request.config || {}; + // Need to extract topP and topK from request.config to avoid duplicate properties being added to the body + // This happens because topP and topK have different property names (top_p and top_k) in the Anthropic API. + // Thinking is extracted separately to avoid type issues. + // ApiVersion is extracted separately as it's not a valid property for the Anthropic API. + const { + topP, + topK, + apiVersion: _, + thinking: defaultThinkingConfig, + ...restConfig + } = request.config ?? {}; const body: BetaMessageCreateParamsNonStreaming = { model: mappedModelName, @@ -314,8 +323,8 @@ export class BetaRunner extends BaseRunner { system: betaSystem, stop_sequences: request.config?.stopSequences, temperature: request.config?.temperature, - top_k: request.config?.topK, - top_p: request.config?.topP, + top_k: topK, + top_p: topP, tool_choice: request.config?.tool_choice, metadata: request.config?.metadata, tools: request.tools?.map((tool) => this.toAnthropicTool(tool)), @@ -329,7 +338,7 @@ export class BetaRunner extends BaseRunner { } : undefined, betas: Array.isArray(request.config?.betas) - ? [...BETA_APIS, ...(request.config?.betas ?? [])] + ? [...(request.config?.betas ?? [])] : [...BETA_APIS], ...restConfig, }; @@ -367,8 +376,17 @@ export class BetaRunner extends BaseRunner { request.config?.thinking ) as BetaMessageCreateParams['thinking'] | undefined; - const { thinking: defaultThinkingConfig, ...restConfig } = - request.config || {}; + // Need to extract topP and topK from request.config to avoid duplicate properties being added to the body + // This happens because topP and topK have different property names (top_p and top_k) in the Anthropic API. + // Thinking is extracted separately to avoid type issues. + // ApiVersion is extracted separately as it's not a valid property for the Anthropic API. + const { + topP, + topK, + apiVersion: _, + thinking: defaultThinkingConfig, + ...restConfig + } = request.config ?? {}; const body: BetaMessageCreateParamsStreaming = { model: mappedModelName, @@ -379,8 +397,8 @@ export class BetaRunner extends BaseRunner { system: betaSystem, stop_sequences: request.config?.stopSequences, temperature: request.config?.temperature, - top_k: request.config?.topK, - top_p: request.config?.topP, + top_k: topK, + top_p: topP, tool_choice: request.config?.tool_choice, metadata: request.config?.metadata, tools: request.tools?.map((tool) => this.toAnthropicTool(tool)), @@ -394,7 +412,7 @@ export class BetaRunner extends BaseRunner { } : undefined, betas: Array.isArray(request.config?.betas) - ? [...BETA_APIS, ...(request.config?.betas ?? [])] + ? [...(request.config?.betas ?? [])] : [...BETA_APIS], ...restConfig, }; diff --git a/js/plugins/anthropic/src/runner/stable.ts b/js/plugins/anthropic/src/runner/stable.ts index 08fda1ee00..3a86288f24 100644 --- a/js/plugins/anthropic/src/runner/stable.ts +++ b/js/plugins/anthropic/src/runner/stable.ts @@ -179,6 +179,12 @@ export class Runner extends BaseRunner { request: GenerateRequest, cacheSystemPrompt?: boolean ): MessageCreateParamsNonStreaming { + if (request.output?.format && request.output.format !== 'text') { + throw new Error( + `Only text output format is supported for Claude models currently` + ); + } + const model = KNOWN_CLAUDE_MODELS[modelName]; const { system, messages } = this.toAnthropicMessages(request.messages); const mappedModelName = @@ -201,8 +207,17 @@ export class Runner extends BaseRunner { request.config?.thinking ) as BetaMessageCreateParams['thinking'] | undefined; - const { thinking: defaultThinkingConfig, ...restConfig } = - request.config || {}; + // Need to extract topP and topK from request.config to avoid duplicate properties being added to the body + // This happens because topP and topK have different property names (top_p and top_k) in the Anthropic API. + // Thinking is extracted separately to avoid type issues. + // ApiVersion is extracted separately as it's not a valid property for the Anthropic API. + const { + topP, + topK, + apiVersion: _, + thinking: defaultThinkingConfig, + ...restConfig + } = request.config ?? {}; const body: MessageCreateParamsNonStreaming = { model: mappedModelName, @@ -212,8 +227,8 @@ export class Runner extends BaseRunner { system: systemValue, stop_sequences: request.config?.stopSequences, temperature: request.config?.temperature, - top_k: request.config?.topK, - top_p: request.config?.topP, + top_k: topK, + top_p: topP, tool_choice: request.config?.tool_choice, metadata: request.config?.metadata, tools: request.tools?.map((tool) => this.toAnthropicTool(tool)), @@ -223,12 +238,6 @@ export class Runner extends BaseRunner { ...restConfig, }; - if (request.output?.format && request.output.format !== 'text') { - throw new Error( - `Only text output format is supported for Claude models currently` - ); - } - return body; } @@ -237,6 +246,12 @@ export class Runner extends BaseRunner { request: GenerateRequest, cacheSystemPrompt?: boolean ): MessageCreateParamsStreaming { + if (request.output?.format && request.output.format !== 'text') { + throw new Error( + `Only text output format is supported for Claude models currently` + ); + } + const model = KNOWN_CLAUDE_MODELS[modelName]; const { system, messages } = this.toAnthropicMessages(request.messages); const mappedModelName = @@ -259,8 +274,17 @@ export class Runner extends BaseRunner { request.config?.thinking ) as BetaMessageCreateParams['thinking'] | undefined; - const { thinking: defaultThinkingConfig, ...restConfig } = - request.config || {}; + // Need to extract topP and topK from request.config to avoid duplicate properties being added to the body + // This happens because topP and topK have different property names (top_p and top_k) in the Anthropic API. + // Thinking is extracted separately to avoid type issues. + // ApiVersion is extracted separately as it's not a valid property for the Anthropic API. + const { + topP, + topK, + apiVersion: _, + thinking: defaultThinkingConfig, + ...restConfig + } = request.config ?? {}; const body: MessageCreateParamsStreaming = { model: mappedModelName, @@ -271,8 +295,8 @@ export class Runner extends BaseRunner { system: systemValue, stop_sequences: request.config?.stopSequences, temperature: request.config?.temperature, - top_k: request.config?.topK, - top_p: request.config?.topP, + top_k: topK, + top_p: topP, tool_choice: request.config?.tool_choice, metadata: request.config?.metadata, tools: request.tools?.map((tool) => this.toAnthropicTool(tool)), @@ -282,12 +306,6 @@ export class Runner extends BaseRunner { ...restConfig, }; - if (request.output?.format && request.output.format !== 'text') { - throw new Error( - `Only text output format is supported for Claude models currently` - ); - } - return body; } diff --git a/js/plugins/anthropic/src/types.ts b/js/plugins/anthropic/src/types.ts index 0e83f15f97..5947bc1a52 100644 --- a/js/plugins/anthropic/src/types.ts +++ b/js/plugins/anthropic/src/types.ts @@ -67,25 +67,41 @@ export interface ClaudeRunnerParams extends ClaudeHelperParamsBase {} export const AnthropicBaseConfigSchema = GenerationCommonConfigSchema.extend({ tool_choice: z .union([ - z.object({ - type: z.literal('auto'), - }), - z.object({ - type: z.literal('any'), - }), - z.object({ - type: z.literal('tool'), - name: z.string(), - }), + z + .object({ + type: z.literal('auto'), + }) + .passthrough(), + z + .object({ + type: z.literal('any'), + }) + .passthrough(), + z + .object({ + type: z.literal('tool'), + name: z.string(), + }) + .passthrough(), ]) + .describe( + 'The tool choice to use for the request. This can be used to specify the tool to use for the request. If not specified, the model will choose the tool to use.' + ) .optional(), metadata: z .object({ user_id: z.string().optional(), }) + .describe('The metadata to include in the request.') + .passthrough() .optional(), /** Optional shorthand to pick API surface for this request. */ - apiVersion: z.enum(['stable', 'beta']).optional(), + apiVersion: z + .enum(['stable', 'beta']) + .optional() + .describe( + 'The API version to use for the request. Both stable and beta features are available on the beta API surface.' + ), }).passthrough(); export type AnthropicBaseConfigSchemaType = typeof AnthropicBaseConfigSchema; @@ -95,6 +111,8 @@ export const ThinkingConfigSchema = z enabled: z.boolean().optional(), budgetTokens: z.number().int().min(1_024).optional(), }) + .passthrough() + .passthrough() .superRefine((value, ctx) => { if (value.enabled && value.budgetTokens === undefined) { ctx.addIssue({ @@ -106,8 +124,10 @@ export const ThinkingConfigSchema = z }); export const AnthropicThinkingConfigSchema = AnthropicBaseConfigSchema.extend({ - thinking: ThinkingConfigSchema.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.' + ), +}).passthrough(); export const AnthropicConfigSchema = AnthropicThinkingConfigSchema; From 14bfcde6c49fd191e6473bffcea4b66ed1549c39 Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Wed, 10 Dec 2025 12:47:39 +0000 Subject: [PATCH 4/6] fix(anthropic): handling of thinking was incorrect. now removes undefined params to prevent possible issues --- js/plugins/anthropic/src/runner/beta.ts | 21 ++++++++----------- js/plugins/anthropic/src/runner/stable.ts | 22 +++++++++----------- js/plugins/anthropic/src/utils.ts | 25 +++++++++++++++++++++++ 3 files changed, 44 insertions(+), 24 deletions(-) create mode 100644 js/plugins/anthropic/src/utils.ts diff --git a/js/plugins/anthropic/src/runner/beta.ts b/js/plugins/anthropic/src/runner/beta.ts index 42d71c1f39..56fae83b9a 100644 --- a/js/plugins/anthropic/src/runner/beta.ts +++ b/js/plugins/anthropic/src/runner/beta.ts @@ -44,6 +44,7 @@ import { logger } from 'genkit/logging'; import { KNOWN_CLAUDE_MODELS, extractVersion } from '../models.js'; import { AnthropicConfigSchema, type ClaudeRunnerParams } from '../types.js'; +import { removeUndefinedProperties } from '../utils.js'; import { BaseRunner } from './base.js'; import { RunnerTypes } from './types.js'; @@ -310,8 +311,8 @@ export class BetaRunner extends BaseRunner { const { topP, topK, - apiVersion: _, - thinking: defaultThinkingConfig, + apiVersion: _1, + thinking: _2, ...restConfig } = request.config ?? {}; @@ -328,9 +329,7 @@ export class BetaRunner extends BaseRunner { tool_choice: request.config?.tool_choice, metadata: request.config?.metadata, tools: request.tools?.map((tool) => this.toAnthropicTool(tool)), - thinking: - (defaultThinkingConfig as BetaMessageCreateParams['thinking']) ?? - thinkingConfig, + thinking: thinkingConfig, output_format: this.isStructuredOutputEnabled(request) ? { type: 'json_schema', @@ -343,7 +342,7 @@ export class BetaRunner extends BaseRunner { ...restConfig, }; - return body; + return removeUndefinedProperties(body); } /** @@ -383,8 +382,8 @@ export class BetaRunner extends BaseRunner { const { topP, topK, - apiVersion: _, - thinking: defaultThinkingConfig, + apiVersion: _1, + thinking: _2, ...restConfig } = request.config ?? {}; @@ -402,9 +401,7 @@ export class BetaRunner extends BaseRunner { tool_choice: request.config?.tool_choice, metadata: request.config?.metadata, tools: request.tools?.map((tool) => this.toAnthropicTool(tool)), - thinking: - (defaultThinkingConfig as BetaMessageCreateParams['thinking']) ?? - thinkingConfig, + thinking: thinkingConfig, output_format: this.isStructuredOutputEnabled(request) ? { type: 'json_schema', @@ -417,7 +414,7 @@ export class BetaRunner extends BaseRunner { ...restConfig, }; - return body; + return removeUndefinedProperties(body); } protected toGenkitResponse(message: BetaMessage): GenerateResponseData { diff --git a/js/plugins/anthropic/src/runner/stable.ts b/js/plugins/anthropic/src/runner/stable.ts index 3a86288f24..7ec0895774 100644 --- a/js/plugins/anthropic/src/runner/stable.ts +++ b/js/plugins/anthropic/src/runner/stable.ts @@ -42,8 +42,10 @@ import { logger } from 'genkit/logging'; import { KNOWN_CLAUDE_MODELS, extractVersion } from '../models.js'; import { AnthropicConfigSchema, type ClaudeRunnerParams } from '../types.js'; +import { removeUndefinedProperties } from '../utils.js'; import { BaseRunner } from './base.js'; import { RunnerTypes as BaseRunnerTypes } from './types.js'; + interface RunnerTypes extends BaseRunnerTypes { Message: Message; Stream: MessageStream; @@ -214,8 +216,8 @@ export class Runner extends BaseRunner { const { topP, topK, - apiVersion: _, - thinking: defaultThinkingConfig, + apiVersion: _1, + thinking: _2, ...restConfig } = request.config ?? {}; @@ -232,13 +234,11 @@ export class Runner extends BaseRunner { tool_choice: request.config?.tool_choice, metadata: request.config?.metadata, tools: request.tools?.map((tool) => this.toAnthropicTool(tool)), - thinking: - (defaultThinkingConfig as BetaMessageCreateParams['thinking']) ?? - thinkingConfig, + thinking: thinkingConfig, ...restConfig, }; - return body; + return removeUndefinedProperties(body); } protected toAnthropicStreamingRequestBody( @@ -281,8 +281,8 @@ export class Runner extends BaseRunner { const { topP, topK, - apiVersion: _, - thinking: defaultThinkingConfig, + apiVersion: _1, + thinking: _2, ...restConfig } = request.config ?? {}; @@ -300,13 +300,11 @@ export class Runner extends BaseRunner { tool_choice: request.config?.tool_choice, metadata: request.config?.metadata, tools: request.tools?.map((tool) => this.toAnthropicTool(tool)), - thinking: - (defaultThinkingConfig as BetaMessageCreateParams['thinking']) ?? - thinkingConfig, + thinking: thinkingConfig, ...restConfig, }; - return body; + return removeUndefinedProperties(body); } protected async createMessage( diff --git a/js/plugins/anthropic/src/utils.ts b/js/plugins/anthropic/src/utils.ts new file mode 100644 index 0000000000..6678eabc19 --- /dev/null +++ b/js/plugins/anthropic/src/utils.ts @@ -0,0 +1,25 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function removeUndefinedProperties(obj: T): T { + if (typeof obj !== 'object' || obj === null) { + return obj; + } + + return Object.fromEntries( + Object.entries(obj).filter(([_, value]) => value !== undefined) + ) as T; +} From 7cd800a9f7d2e6adf7c2b58240a29cef8c7c6665 Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Tue, 16 Dec 2025 16:23:33 +0000 Subject: [PATCH 5/6] fix(anthropic): small correction --- js/plugins/anthropic/src/runner/stable.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/plugins/anthropic/src/runner/stable.ts b/js/plugins/anthropic/src/runner/stable.ts index 7ec0895774..766e0673e5 100644 --- a/js/plugins/anthropic/src/runner/stable.ts +++ b/js/plugins/anthropic/src/runner/stable.ts @@ -16,7 +16,7 @@ import { MessageStream } from '@anthropic-ai/sdk/lib/MessageStream.js'; import type { - MessageCreateParams as BetaMessageCreateParams, + MessageCreateParams, ContentBlock, DocumentBlockParam, ImageBlockParam, @@ -207,7 +207,7 @@ export class Runner extends BaseRunner { const thinkingConfig = this.toAnthropicThinkingConfig( request.config?.thinking - ) as BetaMessageCreateParams['thinking'] | undefined; + ) as MessageCreateParams['thinking'] | undefined; // Need to extract topP and topK from request.config to avoid duplicate properties being added to the body // This happens because topP and topK have different property names (top_p and top_k) in the Anthropic API. @@ -272,7 +272,7 @@ export class Runner extends BaseRunner { const thinkingConfig = this.toAnthropicThinkingConfig( request.config?.thinking - ) as BetaMessageCreateParams['thinking'] | undefined; + ) as MessageCreateParams['thinking'] | undefined; // Need to extract topP and topK from request.config to avoid duplicate properties being added to the body // This happens because topP and topK have different property names (top_p and top_k) in the Anthropic API. From 5f5c6c201d46fcb6c76216a58cd2e7cd71bf05c7 Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Tue, 16 Dec 2025 16:36:35 +0000 Subject: [PATCH 6/6] chore: format --- js/plugins/anthropic/src/runner/stable.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/plugins/anthropic/src/runner/stable.ts b/js/plugins/anthropic/src/runner/stable.ts index 766e0673e5..1496029ebd 100644 --- a/js/plugins/anthropic/src/runner/stable.ts +++ b/js/plugins/anthropic/src/runner/stable.ts @@ -16,11 +16,11 @@ import { MessageStream } from '@anthropic-ai/sdk/lib/MessageStream.js'; import type { - MessageCreateParams, ContentBlock, DocumentBlockParam, ImageBlockParam, Message, + MessageCreateParams, MessageCreateParamsNonStreaming, MessageCreateParamsStreaming, MessageParam,