From 26e623afd6347e02b2141b9740b4cedb007525d1 Mon Sep 17 00:00:00 2001 From: Jacob Cable Date: Tue, 2 Dec 2025 12:12:34 +0000 Subject: [PATCH 1/2] fix: map model suffixes if alias doesn't exist --- js/plugins/anthropic/src/models.ts | 18 +++++++++++++++--- js/plugins/anthropic/tests/beta_runner_test.ts | 2 +- .../anthropic/tests/stable_runner_test.ts | 12 ++++++------ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/js/plugins/anthropic/src/models.ts b/js/plugins/anthropic/src/models.ts index 2ee33d933c..71269b2a9b 100644 --- a/js/plugins/anthropic/src/models.ts +++ b/js/plugins/anthropic/src/models.ts @@ -66,6 +66,16 @@ function commonRef( }); } +/** + * Maps short model names to their full API model IDs. + * Claude 3.x models require versioned names (e.g., claude-3-5-haiku-20241022). + * Claude 4.x models have API aliases that work directly. + */ +const MODEL_VERSION_MAP: Record = { + 'claude-3-haiku': 'claude-3-haiku-20240307', + 'claude-3-5-haiku': 'claude-3-5-haiku-20241022', +}; + export const KNOWN_CLAUDE_MODELS: Record< string, ModelReference< @@ -94,14 +104,16 @@ export const KNOWN_CLAUDE_MODELS: Record< }; /** - * Gets the un-prefixed model name from a modelReference. + * Gets the API model ID from a model name. + * Maps short names to full versioned names for Claude 3.x models. + * Claude 4.x models pass through unchanged as they have API aliases. */ export function extractVersion( model: ModelReference | undefined, modelName: string ): string { - // Extract from model name (remove 'anthropic/' prefix if present) - return modelName.replace(/^anthropic\//, ''); + const cleanName = modelName.replace(/^anthropic\//, ''); + return MODEL_VERSION_MAP[cleanName] ?? cleanName; } /** diff --git a/js/plugins/anthropic/tests/beta_runner_test.ts b/js/plugins/anthropic/tests/beta_runner_test.ts index 655bfc599e..0d549b938c 100644 --- a/js/plugins/anthropic/tests/beta_runner_test.ts +++ b/js/plugins/anthropic/tests/beta_runner_test.ts @@ -499,7 +499,7 @@ describe('BetaRunner', () => { true ); - assert.strictEqual(body.model, 'claude-3-5-haiku'); + assert.strictEqual(body.model, 'claude-3-5-haiku-20241022'); assert.ok(Array.isArray(body.system)); assert.strictEqual(body.max_tokens, 128); assert.strictEqual(body.top_k, 4); diff --git a/js/plugins/anthropic/tests/stable_runner_test.ts b/js/plugins/anthropic/tests/stable_runner_test.ts index 9b60084b3e..72797251b9 100644 --- a/js/plugins/anthropic/tests/stable_runner_test.ts +++ b/js/plugins/anthropic/tests/stable_runner_test.ts @@ -969,7 +969,7 @@ describe('toAnthropicRequestBody', () => { role: 'user', }, ], - model: 'claude-3-5-haiku', + model: 'claude-3-5-haiku-20241022', metadata: { user_id: 'exampleUser123', }, @@ -1003,7 +1003,7 @@ describe('toAnthropicRequestBody', () => { role: 'user', }, ], - model: 'claude-3-haiku', + model: 'claude-3-haiku-20240307', metadata: { user_id: 'exampleUser123', }, @@ -1220,7 +1220,7 @@ describe('toAnthropicStreamingRequestBody', () => { ); assert.strictEqual(output.stream, true); - assert.strictEqual(output.model, 'claude-3-5-haiku'); + assert.strictEqual(output.model, 'claude-3-5-haiku-20241022'); assert.strictEqual(output.max_tokens, 4096); }); @@ -1291,7 +1291,7 @@ describe('claudeRunner', () => { assert.strictEqual(createStub.mock.calls.length, 1); assert.deepStrictEqual(createStub.mock.calls[0].arguments, [ { - model: 'claude-3-5-haiku', + model: 'claude-3-5-haiku-20241022', max_tokens: 4096, messages: [], }, @@ -1342,7 +1342,7 @@ describe('claudeRunner', () => { assert.strictEqual(streamStub.mock.calls.length, 1); assert.deepStrictEqual(streamStub.mock.calls[0].arguments, [ { - model: 'claude-3-5-haiku', + model: 'claude-3-5-haiku-20241022', max_tokens: 4096, messages: [], stream: true, @@ -2202,7 +2202,7 @@ describe('Runner request bodies and error branches', () => { true ); - assert.strictEqual(body.model, 'claude-3-5-haiku'); + assert.strictEqual(body.model, 'claude-3-5-haiku-20241022'); assert.ok(Array.isArray(body.system)); assert.strictEqual(body.system?.[0].cache_control?.type, 'ephemeral'); assert.strictEqual(body.max_tokens, 256); From c1cb45c50a4e5d327beb26051aec584273372a2a Mon Sep 17 00:00:00 2001 From: Jacob Cable Date: Tue, 2 Dec 2025 12:32:29 +0000 Subject: [PATCH 2/2] test(js/plugins/anthropic): add live test --- js/plugins/anthropic/package.json | 1 + js/plugins/anthropic/tests/live_test.ts | 83 +++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 js/plugins/anthropic/tests/live_test.ts diff --git a/js/plugins/anthropic/package.json b/js/plugins/anthropic/package.json index 32b1c4ba87..5b3e19be1f 100644 --- a/js/plugins/anthropic/package.json +++ b/js/plugins/anthropic/package.json @@ -65,6 +65,7 @@ "build:watch": "tsup-node --watch", "test": "tsx --test tests/*_test.ts", "test:file": "tsx --test", + "test:live": "tsx --test tests/live_test.ts", "test:coverage": "check-node-version --node '>=22' && tsx --test --experimental-test-coverage --test-coverage-include='src/**/*.ts' ./tests/**/*_test.ts" } } diff --git a/js/plugins/anthropic/tests/live_test.ts b/js/plugins/anthropic/tests/live_test.ts new file mode 100644 index 0000000000..f008157afe --- /dev/null +++ b/js/plugins/anthropic/tests/live_test.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. + */ + +/** + * Live integration tests that call the real Anthropic API. + * Only runs when ANTHROPIC_API_KEY environment variable is set. + * + * Run with: ANTHROPIC_API_KEY=your-key pnpm test:live + */ + +import * as assert from 'assert'; +import { genkit } from 'genkit'; +import { describe, it } from 'node:test'; +import { anthropic } from '../src/index.js'; + +const API_KEY = process.env.ANTHROPIC_API_KEY; + +describe('Live Anthropic API Tests', { skip: !API_KEY }, () => { + it('should work with short model name claude-3-5-haiku', async () => { + const ai = genkit({ + plugins: [anthropic({ apiKey: API_KEY })], + }); + + const result = await ai.generate({ + model: 'anthropic/claude-3-5-haiku', + prompt: 'Say "hello" and nothing else.', + }); + + assert.ok(result.text.toLowerCase().includes('hello')); + }); + + it('should work with short model name claude-3-haiku', async () => { + const ai = genkit({ + plugins: [anthropic({ apiKey: API_KEY })], + }); + + const result = await ai.generate({ + model: 'anthropic/claude-3-haiku', + prompt: 'Say "hello" and nothing else.', + }); + + assert.ok(result.text.toLowerCase().includes('hello')); + }); + + it('should work with full versioned model name', async () => { + const ai = genkit({ + plugins: [anthropic({ apiKey: API_KEY })], + }); + + const result = await ai.generate({ + model: 'anthropic/claude-3-5-haiku-20241022', + prompt: 'Say "hello" and nothing else.', + }); + + assert.ok(result.text.toLowerCase().includes('hello')); + }); + + it('should work with anthropic.model() helper', async () => { + const ai = genkit({ + plugins: [anthropic({ apiKey: API_KEY })], + }); + + const result = await ai.generate({ + model: anthropic.model('claude-3-5-haiku'), + prompt: 'Say "hello" and nothing else.', + }); + + assert.ok(result.text.toLowerCase().includes('hello')); + }); +});