Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions TESTING_UNKNOWN_MODELS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Testing Unknown Model Fallback Feature

## Overview
This guide explains how to test the fix for issue #5544: Fallback for unsupported model versions.

## What Changed
Previously, the AI Gateway would return a 500 error when encountering an unknown model version. Now, when a provider is explicitly specified (e.g., `new-model-v2/openai`), the gateway will attempt to proxy the request to the target API even if the model is not in Helicone's registry.

## Code Changes

### 1. `packages/cost/models/provider-helpers.ts`
- Modified `parseModelString` to allow unknown models when a provider is specified
- Previously: Unknown models without providers would fail
- Now: Unknown models WITH providers are allowed (for passthrough)

### 2. `worker/src/lib/ai-gateway/AttemptBuilder.ts`
- Added logging when attempting passthrough for unknown models
- Enhanced error messages for better debugging
- Improved passthrough attempt handling

## Manual Testing Steps

### Prerequisites
1. Start the Helicone development environment:
```bash
./helicone-compose.sh helicone up
cd web && yarn dev:better-auth
cd valhalla/jawn && yarn dev
```

2. Configure provider API keys in Supabase (provider_keys table)

### Test Case 1: Unknown OpenAI Model Version
**Expected:** Request should be proxied to OpenAI API even though model is not in registry

```bash
curl -X POST http://localhost:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your-helicone-api-key>" \
-d '{
"model": "gpt-5-turbo-preview/openai",
"messages": [{"role": "user", "content": "Hello!"}]
}'
```

**Expected Result:**
- Request is proxied to OpenAI API
- Console logs show: `Model "gpt-5-turbo-preview" not found in registry, attempting passthrough to openai`
- If the model exists on OpenAI's side: Success response
- If OpenAI returns an error: That error is returned to the user (not a Helicone 500 error)

### Test Case 2: Unknown Anthropic Model Version
**Expected:** Request should be proxied to Anthropic API

```bash
curl -X POST http://localhost:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your-helicone-api-key>" \
-d '{
"model": "claude-4-opus-20250301/anthropic",
"messages": [{"role": "user", "content": "Hello!"}]
}'
```

**Expected Result:**
- Request is proxied to Anthropic API (converted to Anthropic format)
- Console logs show passthrough attempt
- Anthropic's actual error (if model doesn't exist) is returned to user

### Test Case 3: Unknown Model WITHOUT Provider
**Expected:** Should return helpful error message asking user to specify provider

```bash
curl -X POST http://localhost:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your-helicone-api-key>" \
-d '{
"model": "unknown-model-12345",
"messages": [{"role": "user", "content": "Hello!"}]
}'
```

**Expected Result:**
- Returns 400 error
- Error message: "Unknown model: unknown-model-12345. Please specify a provider (e.g., unknown-model-12345/openai) or use a supported model."

### Test Case 4: Fallback from Unknown to Known Model
**Expected:** Try unknown model first, fallback to known model on error

```bash
curl -X POST http://localhost:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your-helicone-api-key>" \
-d '{
"model": "gpt-5-experimental/openai,gpt-4o/openai",
"messages": [{"role": "user", "content": "Hello!"}]
}'
```

**Expected Result:**
- First attempts `gpt-5-experimental/openai` (likely fails)
- Falls back to `gpt-4o/openai` (should succeed)
- Returns successful response from gpt-4o

### Test Case 5: Multiple Unknown Models with Fallback
**Expected:** Try each model in sequence until one succeeds

```bash
curl -X POST http://localhost:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your-helicone-api-key>" \
-d '{
"model": "fake-model-1/openai,fake-model-2/anthropic,gpt-4o/openai",
"messages": [{"role": "user", "content": "Hello!"}]
}'
```

**Expected Result:**
- Tries `fake-model-1/openai` (fails)
- Tries `fake-model-2/anthropic` (fails)
- Falls back to `gpt-4o/openai` (succeeds)

## Verification Checklist

- [ ] Unknown models with providers are proxied (not rejected with 500)
- [ ] Unknown models without providers show helpful error message
- [ ] Fallback mechanism works (tries next model on failure)
- [ ] Console logs show passthrough attempts
- [ ] Requests are properly logged in Helicone
- [ ] Provider errors are passed through to users (not masked by Helicone)
- [ ] Known models still work as expected (no regression)

## Automated Tests

### Model Parser Tests (✅ Passing)
```bash
cd packages && npx jest __tests__/cost/model-parser.test.ts
```

Tests verify that:
- Unknown models without providers are rejected
- Unknown models WITH providers are accepted
- Model name mappings still work
- :online suffix handling works

## Implementation Notes

### Logging
The implementation adds console.log statements for debugging:
- When a model is not found in registry
- When creating a passthrough endpoint
- When passthrough fails

These logs help understand what's happening during development and debugging.

### No Breaking Changes
This change is backwards compatible:
- Known models work exactly as before
- Unknown models without providers still show helpful error (no change)
- NEW: Unknown models WITH providers now work via passthrough (instead of 500 error)

## Success Criteria

✅ **Issue #5544 is resolved when:**
1. Gateway does NOT return 500 for unknown models with providers
2. Gateway attempts to proxy request to target API
3. Logging shows passthrough attempts
4. Model parser tests pass
5. Manual tests confirm passthrough behavior works
57 changes: 57 additions & 0 deletions packages/__tests__/cost/model-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,63 @@ describe("parseModelString", () => {
});
});

describe("unknown model handling", () => {
it("should reject unknown model without provider", () => {
const result = parseModelString("unknown-model-12345");

expect(result.error).toContain("Unknown model");
expect(result.error).toContain("Please specify a provider");
expect(result.data).toBeNull();
});

it("should allow unknown model with valid provider (passthrough)", () => {
const result = parseModelString("gpt-5-turbo-preview/openai");

expect(result.error).toBeNull();
expect(result.data).toEqual({
modelName: "gpt-5-turbo-preview",
provider: "openai",
isOnline: false,
});
});

it("should allow unknown Anthropic model with provider (passthrough)", () => {
const result = parseModelString("claude-4-opus-20250301/anthropic");

expect(result.error).toBeNull();
expect(result.data).toEqual({
modelName: "claude-4-opus-20250301",
provider: "anthropic",
isOnline: false,
});
});

it("should allow unknown model with provider and customUid (passthrough)", () => {
const result = parseModelString(
"unknown-model-version/openai/custom-uid-123"
);

expect(result.error).toBeNull();
expect(result.data).toEqual({
modelName: "unknown-model-version",
provider: "openai",
customUid: "custom-uid-123",
isOnline: false,
});
});

it("should allow unknown model with :online suffix and provider (passthrough)", () => {
const result = parseModelString("new-model-v2:online/anthropic");

expect(result.error).toBeNull();
expect(result.data).toEqual({
modelName: "new-model-v2",
provider: "anthropic",
isOnline: true,
});
});
});

describe("model name mappings for backward compatibility", () => {
it("should map gemini-1.5-flash to gemini-2.5-flash-lite", () => {
const result = parseModelString("gemini-1.5-flash");
Expand Down
2 changes: 2 additions & 0 deletions packages/cost/models/provider-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,8 @@ export function parseModelString(
);
}

// When provider is specified, allow unknown models (will use passthrough)
// This enables using new model versions without waiting for registry updates
return ok({
modelName,
provider,
Expand Down
14 changes: 14 additions & 0 deletions worker/src/lib/ai-gateway/AttemptBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ export class AttemptBuilder {

if (!providerDataResult.data) {
// No registry data - try passthrough for unknown models
console.log(
`Model "${modelSpec.modelName}" not found in registry, attempting passthrough to ${modelSpec.provider}`
);
return this.buildPassthroughAttempt(
modelSpec,
orgId,
Expand Down Expand Up @@ -272,6 +275,9 @@ export class AttemptBuilder {
);

if (!userKey || !this.isByokEnabled(userKey)) {
console.warn(
`No BYOK key available for passthrough to ${modelSpec.provider} for model ${modelSpec.modelName}`
);
return []; // No BYOK available for passthrough
}

Expand All @@ -289,6 +295,10 @@ export class AttemptBuilder {
);

if (!isErr(passthroughResult) && passthroughResult.data) {
console.log(
`Created passthrough endpoint for unknown model: ${modelSpec.modelName}/${modelSpec.provider}`
);

// Process plugins using PluginHandler
const processedPlugins = this.pluginHandler.processPlugins(
modelSpec,
Expand All @@ -308,6 +318,10 @@ export class AttemptBuilder {
];
}

console.error(
`Failed to create passthrough endpoint for ${modelSpec.modelName}/${modelSpec.provider}:`,
passthroughResult.error
);
return [];
}

Expand Down
Loading