Files
wehub-resource-sync 0d3cb498a3
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Waiting to run
Test and Publish Multi-arch Docker Image / test (push) Waiting to run
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Blocked by required conditions
Validate Renovate Config / Validate Renovate Configuration (push) Waiting to run
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

839 lines
28 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { clearCache } from '../../src/cache';
import {
createTrueFoundryProvider,
TrueFoundryEmbeddingProvider,
TrueFoundryProvider,
} from '../../src/providers/truefoundry';
import * as fetchModule from '../../src/util/fetch/index';
import { mockProcessEnv } from '../util/utils';
const TRUEFOUNDRY_API_BASE = 'https://llm-gateway.truefoundry.com';
vi.mock('../../src/util', async (importOriginal) => {
return {
...(await importOriginal()),
maybeLoadFromExternalFile: vi.fn((x) => x),
renderVarsInObject: vi.fn((x) => x),
};
});
vi.mock('../../src/util/fetch/index.ts');
describe('TrueFoundry', () => {
const mockedFetchWithRetries = vi.mocked(fetchModule.fetchWithRetries);
afterEach(async () => {
await clearCache();
vi.clearAllMocks();
});
describe('TrueFoundryProvider', () => {
const provider = new TrueFoundryProvider('openai/gpt-4', {});
it('should initialize with correct model name', () => {
expect(provider.modelName).toBe('openai/gpt-4');
});
describe('isReasoningModel', () => {
it('should detect GPT-5 models with provider prefix', () => {
const provider = new TrueFoundryProvider('openai/gpt-5-nano', {});
expect((provider as any).isReasoningModel()).toBe(true);
});
it('should detect GPT-5 models without provider prefix', () => {
const provider = new TrueFoundryProvider('gpt-5', {});
expect((provider as any).isReasoningModel()).toBe(true);
});
it('should detect o1 models with provider prefix', () => {
const provider = new TrueFoundryProvider('openai/o1-preview', {});
expect((provider as any).isReasoningModel()).toBe(true);
});
it('should detect o1 models without provider prefix', () => {
const provider = new TrueFoundryProvider('o1-mini', {});
expect((provider as any).isReasoningModel()).toBe(true);
});
it('should not detect GPT-4 as reasoning model', () => {
const provider = new TrueFoundryProvider('openai/gpt-4', {});
expect((provider as any).isReasoningModel()).toBe(false);
});
it('should not detect Claude as reasoning model', () => {
const provider = new TrueFoundryProvider('anthropic/claude-sonnet-4', {});
expect((provider as any).isReasoningModel()).toBe(false);
});
it('should not detect Gemini as reasoning model', () => {
const provider = new TrueFoundryProvider('vertex-ai/gemini-2.5-pro', {});
expect((provider as any).isReasoningModel()).toBe(false);
});
});
it('should return correct id', () => {
expect(provider.id()).toBe('truefoundry:openai/gpt-4');
});
it('should return correct string representation', () => {
expect(provider.toString()).toBe('[TrueFoundry Provider openai/gpt-4]');
});
it('should serialize to JSON correctly without API key', () => {
const provider = new TrueFoundryProvider('openai/gpt-4', {
config: {
temperature: 0.7,
max_tokens: 100,
},
});
expect(provider.toJSON()).toEqual({
provider: 'truefoundry',
model: 'openai/gpt-4',
config: {
temperature: 0.7,
max_tokens: 100,
apiKeyEnvar: 'TRUEFOUNDRY_API_KEY',
apiBaseUrl: TRUEFOUNDRY_API_BASE,
},
});
});
it('should serialize to JSON correctly with API key redacted', () => {
const provider = new TrueFoundryProvider('openai/gpt-4', {
config: {
apiKey: 'secret-api-key',
temperature: 0.7,
},
});
const json = provider.toJSON();
expect(json).toEqual({
provider: 'truefoundry',
model: 'openai/gpt-4',
config: {
temperature: 0.7,
apiKey: undefined,
apiKeyEnvar: 'TRUEFOUNDRY_API_KEY',
apiBaseUrl: TRUEFOUNDRY_API_BASE,
},
});
});
it('should handle TrueFoundry-specific metadata configuration', () => {
const provider = new TrueFoundryProvider('openai/gpt-4', {
config: {
metadata: {
user_id: 'test-user',
custom_key: 'custom_value',
},
},
});
expect(provider.toJSON().config).toMatchObject({
metadata: {
user_id: 'test-user',
custom_key: 'custom_value',
},
});
});
it('should handle TrueFoundry-specific logging configuration', () => {
const provider = new TrueFoundryProvider('openai/gpt-4', {
config: {
loggingConfig: {
enabled: true,
},
},
});
expect(provider.toJSON().config).toMatchObject({
loggingConfig: {
enabled: true,
},
});
});
it('should use default apiBaseUrl when not specified', () => {
const provider = new TrueFoundryProvider('openai/gpt-4', {});
expect(provider.toJSON().config.apiBaseUrl).toBe('https://llm-gateway.truefoundry.com');
});
it('should use custom apiBaseUrl when specified', () => {
const provider = new TrueFoundryProvider('openai/gpt-4', {
config: {
apiBaseUrl: 'https://custom-gateway.example.com',
},
});
expect(provider.toJSON().config.apiBaseUrl).toBe('https://custom-gateway.example.com');
});
describe('callApi', () => {
beforeEach(() => {
mockProcessEnv({ TRUEFOUNDRY_API_KEY: 'test-key' });
});
afterEach(() => {
mockProcessEnv({ TRUEFOUNDRY_API_KEY: undefined });
});
it('should call TrueFoundry API and return output with correct structure', async () => {
const mockResponse = {
choices: [{ message: { content: 'Test output' } }],
usage: { total_tokens: 10, prompt_tokens: 5, completion_tokens: 5 },
};
const response = new Response(JSON.stringify(mockResponse), {
status: 200,
statusText: 'OK',
headers: new Headers({ 'Content-Type': 'application/json' }),
});
mockedFetchWithRetries.mockResolvedValueOnce(response);
const result = await provider.callApi('Test prompt');
const expectedBody = {
model: 'openai/gpt-4',
messages: [{ role: 'user', content: 'Test prompt' }],
max_tokens: 1024,
temperature: 0,
};
expect(mockedFetchWithRetries).toHaveBeenCalledWith(
`${TRUEFOUNDRY_API_BASE}/chat/completions`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer test-key',
},
body: JSON.stringify(expectedBody),
},
300000,
undefined,
);
expect(result).toEqual({
output: 'Test output',
tokenUsage: {
total: 10,
prompt: 5,
completion: 5,
numRequests: 1,
},
cached: false,
cost: undefined,
latencyMs: expect.any(Number),
logProbs: undefined,
guardrails: {
flagged: false,
},
metadata: expect.any(Object),
});
expect(result.latencyMs).toBeGreaterThanOrEqual(0);
});
it('should add X-TFY-METADATA header when metadata is provided', async () => {
const providerWithMetadata = new TrueFoundryProvider('openai/gpt-4', {
config: {
metadata: {
user_id: 'test-user',
custom_field: 'test-value',
},
},
});
const mockResponse = {
choices: [{ message: { content: 'Test output' } }],
usage: { total_tokens: 10, prompt_tokens: 5, completion_tokens: 5 },
};
const response = new Response(JSON.stringify(mockResponse), {
status: 200,
statusText: 'OK',
headers: new Headers({ 'Content-Type': 'application/json' }),
});
mockedFetchWithRetries.mockResolvedValueOnce(response);
await providerWithMetadata.callApi('Test prompt');
const lastCall = mockedFetchWithRetries.mock.calls[0];
if (!lastCall) {
throw new Error('Expected fetch to have been called');
}
const requestOptions = lastCall[1] as { headers: Record<string, string> };
expect(requestOptions.headers['X-TFY-METADATA']).toBe(
JSON.stringify({
user_id: 'test-user',
custom_field: 'test-value',
}),
);
});
it('should add X-TFY-LOGGING-CONFIG header when loggingConfig is provided', async () => {
const providerWithLogging = new TrueFoundryProvider('openai/gpt-4', {
config: {
loggingConfig: {
enabled: true,
},
},
});
const mockResponse = {
choices: [{ message: { content: 'Test output' } }],
usage: { total_tokens: 10, prompt_tokens: 5, completion_tokens: 5 },
};
const response = new Response(JSON.stringify(mockResponse), {
status: 200,
statusText: 'OK',
headers: new Headers({ 'Content-Type': 'application/json' }),
});
mockedFetchWithRetries.mockResolvedValueOnce(response);
await providerWithLogging.callApi('Test prompt');
const lastCall = mockedFetchWithRetries.mock.calls[0];
if (!lastCall) {
throw new Error('Expected fetch to have been called');
}
const requestOptions = lastCall[1] as { headers: Record<string, string> };
expect(requestOptions.headers['X-TFY-LOGGING-CONFIG']).toBe(
JSON.stringify({
enabled: true,
}),
);
});
it('should add both TrueFoundry headers when both configs are provided', async () => {
const providerWithBoth = new TrueFoundryProvider('openai/gpt-4', {
config: {
metadata: { user_id: 'test-user' },
loggingConfig: { enabled: true },
},
});
const mockResponse = {
choices: [{ message: { content: 'Test output' } }],
usage: { total_tokens: 10, prompt_tokens: 5, completion_tokens: 5 },
};
const response = new Response(JSON.stringify(mockResponse), {
status: 200,
statusText: 'OK',
headers: new Headers({ 'Content-Type': 'application/json' }),
});
mockedFetchWithRetries.mockResolvedValueOnce(response);
await providerWithBoth.callApi('Test prompt');
const lastCall = mockedFetchWithRetries.mock.calls[0];
if (!lastCall) {
throw new Error('Expected fetch to have been called');
}
const requestOptions = lastCall[1] as { headers: Record<string, string> };
expect(requestOptions.headers['X-TFY-METADATA']).toBe(
JSON.stringify({ user_id: 'test-user' }),
);
expect(requestOptions.headers['X-TFY-LOGGING-CONFIG']).toBe(
JSON.stringify({ enabled: true }),
);
});
it('should use cache by default', async () => {
const mockResponse = {
choices: [{ message: { content: 'Cached output' } }],
usage: { total_tokens: 10, prompt_tokens: 5, completion_tokens: 5 },
};
const response = new Response(JSON.stringify(mockResponse), {
status: 200,
statusText: 'OK',
headers: new Headers({ 'Content-Type': 'application/json' }),
});
mockedFetchWithRetries.mockResolvedValue(response);
await provider.callApi('Test prompt');
const cachedResult = await provider.callApi('Test prompt');
expect(mockedFetchWithRetries).toHaveBeenCalledTimes(1);
expect(cachedResult).toEqual({
output: 'Cached output',
cached: true,
cost: undefined,
latencyMs: expect.any(Number),
logProbs: undefined,
guardrails: {
flagged: false,
},
// Cached responses don't count as new requests, so numRequests is not included
tokenUsage: {
total: 10,
cached: 10,
},
metadata: expect.any(Object),
});
expect(cachedResult.latencyMs).toBeGreaterThanOrEqual(0);
});
it('should handle API errors', async () => {
const errorResponse = {
error: {
message: 'API Error',
type: 'invalid_request_error',
},
};
const response = new Response(JSON.stringify(errorResponse), {
status: 400,
statusText: 'Bad Request',
headers: new Headers({ 'Content-Type': 'application/json' }),
});
mockedFetchWithRetries.mockResolvedValueOnce(response);
const result = await provider.callApi('Test prompt');
expect(result.error).toContain('400 Bad Request');
});
it('should normalize TrueFoundry guardrail failures into flagged responses', async () => {
const guardrailResponse = {
error: {
message: 'Guardrail violation detected',
type: 'guardrail_checks_failed',
},
guardrail_checks: {
llm_input_guardrails: [{ name: 'safety/prompt-injection' }],
},
};
const response = new Response(JSON.stringify(guardrailResponse), {
status: 400,
statusText: 'Bad Request',
headers: new Headers({ 'Content-Type': 'application/json' }),
});
mockedFetchWithRetries.mockResolvedValueOnce(response);
const result = await provider.callApi('Test prompt');
expect(result.error).toBeUndefined();
expect(result.output).toBe('Guardrail violation detected');
expect(result.isRefusal).toBe(true);
expect(result.guardrails).toEqual({
flagged: true,
flaggedInput: true,
flaggedOutput: false,
reason: 'Guardrail violation detected',
});
expect(result.metadata?.http).toMatchObject({
status: 400,
statusText: 'Bad Request',
});
});
it('should normalize Azure safety blocks proxied through TrueFoundry', async () => {
const guardrailResponse = {
status: 'failure',
message:
"azure-foundry error: Response content blocked by label 'MultiSeverity_HateSpeechScore'.",
error: {
message:
"azure-foundry error: Response content blocked by label 'MultiSeverity_HateSpeechScore'.",
type: 'APIError',
code: '400',
},
error_origin_level: 'api_error',
provider: 'azure-foundry',
};
const response = new Response(JSON.stringify(guardrailResponse), {
status: 400,
statusText: 'Bad Request',
headers: new Headers({ 'Content-Type': 'application/json' }),
});
mockedFetchWithRetries.mockResolvedValueOnce(response);
const result = await provider.callApi('Test prompt');
expect(result.error).toBeUndefined();
expect(result.output).toBe(
"azure-foundry error: Response content blocked by label 'MultiSeverity_HateSpeechScore'.",
);
expect(result.isRefusal).toBe(true);
expect(result.guardrails).toEqual({
flagged: true,
flaggedInput: false,
flaggedOutput: true,
reason:
"azure-foundry error: Response content blocked by label 'MultiSeverity_HateSpeechScore'.",
});
});
it('should not guess a direction for ambiguous downstream safety blocks', async () => {
const guardrailResponse = {
error: {
message: 'Safety policy rejected request',
code: 'content_filter',
innererror: {
code: 'ResponsibleAIPolicyViolation',
},
},
};
const response = new Response(JSON.stringify(guardrailResponse), {
status: 400,
statusText: 'Bad Request',
headers: new Headers({ 'Content-Type': 'application/json' }),
});
mockedFetchWithRetries.mockResolvedValueOnce(response);
const result = await provider.callApi('Test prompt');
expect(result.error).toBeUndefined();
expect(result.output).toBe('Safety policy rejected request');
expect(result.isRefusal).toBe(true);
expect(result.guardrails).toEqual({
flagged: true,
reason: 'Safety policy rejected request',
});
});
it.each([
'Content filtering system is down or otherwise unable to complete the request in time',
'Unable to determine whether the response was filtered because the content filtering system timed out',
"Unable to determine whether response content blocked by label 'MultiSeverity_HateSpeechScore' because the content filtering system timed out",
'Content management policy check did not complete because the content filtering system timed out',
'Responsible AI policy check did not complete because the content filtering system timed out',
])('should preserve downstream content filter failures as API errors: %s', async (message) => {
const errorResponse = {
error: {
message,
code: 'content_filter_error',
},
};
const response = new Response(JSON.stringify(errorResponse), {
status: 400,
statusText: 'Bad Request',
headers: new Headers({ 'Content-Type': 'application/json' }),
});
mockedFetchWithRetries.mockResolvedValueOnce(response);
const result = await provider.callApi('Test prompt');
expect(result.error).toContain('400 Bad Request');
expect(result.error).toContain('content_filter_error');
expect(result.isRefusal).toBeUndefined();
expect(result.guardrails).toBeUndefined();
});
it('should preserve nested downstream content filter failures as API errors', async () => {
const errorResponse = {
error: {
message:
'Unable to determine whether the response was filtered because the content filtering system timed out',
code: '400',
innererror: {
code: 'content_filter_error',
},
},
};
const response = new Response(JSON.stringify(errorResponse), {
status: 400,
statusText: 'Bad Request',
headers: new Headers({ 'Content-Type': 'application/json' }),
});
mockedFetchWithRetries.mockResolvedValueOnce(response);
const result = await provider.callApi('Test prompt');
expect(result.error).toContain('400 Bad Request');
expect(result.error).toContain('content_filter_error');
expect(result.isRefusal).toBeUndefined();
expect(result.guardrails).toBeUndefined();
});
it('should preserve structured downstream guardrail blocks when a filter error is present', async () => {
const guardrailResponse = {
error: {
message:
'Unable to determine whether the response was filtered because the content filtering system timed out',
code: 'content_filter_error',
content_filter_results: {
hate: {
filtered: true,
severity: 'high',
},
},
},
};
const response = new Response(JSON.stringify(guardrailResponse), {
status: 400,
statusText: 'Bad Request',
headers: new Headers({ 'Content-Type': 'application/json' }),
});
mockedFetchWithRetries.mockResolvedValueOnce(response);
const result = await provider.callApi('Test prompt');
expect(result.error).toBeUndefined();
expect(result.isRefusal).toBe(true);
expect(result.guardrails).toEqual({
flagged: true,
flaggedInput: false,
flaggedOutput: true,
reason:
'Unable to determine whether the response was filtered because the content filtering system timed out',
});
});
it('should handle network errors', async () => {
mockedFetchWithRetries.mockRejectedValueOnce(new Error('Network error'));
const result = await provider.callApi('Test prompt');
expect(result.error).toContain('Network error');
});
it('should handle rate limit errors', async () => {
const rateLimitResponse = new Response(
JSON.stringify({
error: {
message: 'Rate limit exceeded',
type: 'rate_limit_error',
},
}),
{
status: 429,
statusText: 'Too Many Requests',
headers: new Headers({ 'Content-Type': 'application/json' }),
},
);
mockedFetchWithRetries.mockResolvedValueOnce(rateLimitResponse);
const result = await provider.callApi('Test prompt');
expect(result.error).toContain('429');
expect(result.error).toContain('Rate limit exceeded');
});
});
});
describe('TrueFoundryEmbeddingProvider', () => {
const embeddingProvider = new TrueFoundryEmbeddingProvider('openai/text-embedding-3-large', {});
beforeEach(() => {
mockProcessEnv({ TRUEFOUNDRY_API_KEY: 'test-key' });
});
afterEach(() => {
mockProcessEnv({ TRUEFOUNDRY_API_KEY: undefined });
});
it('should initialize with correct model name', () => {
expect(embeddingProvider.modelName).toBe('openai/text-embedding-3-large');
});
it('should return correct id', () => {
expect(embeddingProvider.id()).toBe('truefoundry:openai/text-embedding-3-large');
});
it('should return correct string representation', () => {
expect(embeddingProvider.toString()).toBe(
'[TrueFoundry Embedding Provider openai/text-embedding-3-large]',
);
});
it('should serialize to JSON correctly', () => {
const provider = new TrueFoundryEmbeddingProvider('openai/text-embedding-3-large', {
config: {
apiKey: 'secret-key',
},
});
expect(provider.toJSON()).toEqual({
provider: 'truefoundry',
model: 'openai/text-embedding-3-large',
config: {
apiKey: undefined,
apiKeyEnvar: 'TRUEFOUNDRY_API_KEY',
apiBaseUrl: TRUEFOUNDRY_API_BASE,
},
});
});
it('should call embedding API successfully', async () => {
const mockResponse = {
data: [
{
embedding: [0.1, 0.2, 0.3],
index: 0,
},
],
usage: {
prompt_tokens: 5,
total_tokens: 5,
},
};
const response = new Response(JSON.stringify(mockResponse), {
status: 200,
statusText: 'OK',
headers: new Headers({ 'Content-Type': 'application/json' }),
});
mockedFetchWithRetries.mockResolvedValueOnce(response);
const result = await embeddingProvider.callEmbeddingApi('Test text');
expect(mockedFetchWithRetries).toHaveBeenCalledWith(
`${TRUEFOUNDRY_API_BASE}/embeddings`,
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({
'Content-Type': 'application/json',
Authorization: 'Bearer test-key',
}),
}),
300000,
undefined,
);
expect(result).toEqual({
embedding: [0.1, 0.2, 0.3],
latencyMs: expect.any(Number),
tokenUsage: {
total: 5,
prompt: 5,
completion: 0,
numRequests: 1,
},
});
expect(result.latencyMs).toBeGreaterThanOrEqual(0);
});
it('should add TrueFoundry headers to embedding requests', async () => {
const providerWithHeaders = new TrueFoundryEmbeddingProvider(
'openai/text-embedding-3-large',
{
config: {
metadata: { user_id: 'test-user' },
loggingConfig: { enabled: true },
},
},
);
const mockResponse = {
data: [
{
embedding: [0.1, 0.2, 0.3],
index: 0,
},
],
usage: {
prompt_tokens: 5,
total_tokens: 5,
},
};
const response = new Response(JSON.stringify(mockResponse), {
status: 200,
statusText: 'OK',
headers: new Headers({ 'Content-Type': 'application/json' }),
});
mockedFetchWithRetries.mockResolvedValueOnce(response);
await providerWithHeaders.callEmbeddingApi('Test text');
const lastCall = mockedFetchWithRetries.mock.calls[0];
if (!lastCall) {
throw new Error('Expected fetch to have been called');
}
const requestOptions = lastCall[1] as { headers: Record<string, string> };
expect(requestOptions.headers['X-TFY-METADATA']).toBe(
JSON.stringify({ user_id: 'test-user' }),
);
expect(requestOptions.headers['X-TFY-LOGGING-CONFIG']).toBe(
JSON.stringify({ enabled: true }),
);
});
it('should handle embedding API errors', async () => {
const errorResponse = {
error: {
message: 'Invalid model',
type: 'invalid_request_error',
},
};
const response = new Response(JSON.stringify(errorResponse), {
status: 400,
statusText: 'Bad Request',
headers: new Headers({ 'Content-Type': 'application/json' }),
});
mockedFetchWithRetries.mockResolvedValueOnce(response);
const result = await embeddingProvider.callEmbeddingApi('Test text');
expect(result.error).toBeDefined();
});
it('should use default apiBaseUrl when not specified', () => {
const provider = new TrueFoundryEmbeddingProvider('openai/text-embedding-3-large', {});
expect(provider.toJSON().config.apiBaseUrl).toBe('https://llm-gateway.truefoundry.com');
});
it('should use custom apiBaseUrl when specified', () => {
const provider = new TrueFoundryEmbeddingProvider('openai/text-embedding-3-large', {
config: {
apiBaseUrl: 'https://custom-gateway.example.com',
},
});
expect(provider.toJSON().config.apiBaseUrl).toBe('https://custom-gateway.example.com');
});
});
describe('createTrueFoundryProvider', () => {
it('should create chat provider for non-embedding models', () => {
const provider = createTrueFoundryProvider('truefoundry:openai/gpt-4', {
config: {
config: { temperature: 0.5 },
},
});
expect(provider).toBeInstanceOf(TrueFoundryProvider);
expect((provider as TrueFoundryProvider).modelName).toBe('openai/gpt-4');
});
it('should create embedding provider for embedding models', () => {
const provider = createTrueFoundryProvider('truefoundry:openai/text-embedding-3-large', {
config: {
config: { temperature: 0.5 },
},
});
expect(provider).toBeInstanceOf(TrueFoundryEmbeddingProvider);
expect((provider as TrueFoundryEmbeddingProvider).modelName).toBe(
'openai/text-embedding-3-large',
);
});
it('should pass config options correctly to provider', () => {
const provider = createTrueFoundryProvider('truefoundry:openai/gpt-4', {
config: {
config: {
temperature: 0.8,
apiBaseUrl: 'https://custom.example.com',
},
},
env: { CUSTOM_VAR: 'test' },
});
const json = (provider as TrueFoundryProvider).toJSON();
expect(json.config.temperature).toBe(0.8);
expect(json.config.apiBaseUrl).toBe('https://custom.example.com');
});
it('should handle model names with colons', () => {
const provider = createTrueFoundryProvider('truefoundry:provider:model:version', {});
expect((provider as TrueFoundryProvider).modelName).toBe('provider:model:version');
});
});
});