learning_ai_common_plat/packages/llm/src/factory.ts
root 39e48f3241 feat(llm): add FallbackLLMProvider + release pipeline script
- packages/llm: add FallbackLLMProvider (providers/fallback.ts) that
  tries each provider in order, skipping unconfigured or erroring ones;
  wire 'fallback' as a first-class LLMProviderType in factory + types
- packages/llm: improve auto-detection in factory — PERPLEXITY_API_KEY
  and GEMINI_API_KEY trigger auto-selection when no explicit provider set
- scripts/release.sh: new pipeline — rebase from origin/main, build,
  apply changesets, publish outdated packages to Gitea registry, push
- scripts/run-registry-tests.sh: fix Gitea URL health-check to use a
  real package endpoint with auth header instead of bare registry root
- docs: mark Vercel track-B prompts B1–B3 as complete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 04:21:12 +00:00

106 lines
3.4 KiB
TypeScript

/**
* LLM provider factory.
*
* Creates an LLMProvider based on LLM_PROVIDER env var.
* Auto-detects provider from endpoint/key env vars if not explicitly set.
*
* Provider selection priority:
* LLM_PROVIDER env var > auto-detect from endpoint/key env vars > openai
*
* To use a fallback chain (e.g. perplexity → openai → gemini), set:
* LLM_PROVIDER=fallback
* LLM_FALLBACK_ORDER=perplexity,openai,gemini (default if unset)
*/
import { AzureOpenAIProvider } from './providers/azure-openai.js';
import { FallbackLLMProvider } from './providers/fallback.js';
import { GeminiProvider } from './providers/gemini.js';
import { MockLLMProvider } from './providers/mock.js';
import { OpenAIProvider } from './providers/openai.js';
import { PerplexityProvider } from './providers/perplexity.js';
import type { LLMProvider, LLMProviderType } from './types.js';
let _provider: LLMProvider | null = null;
/**
* Resolve provider type from env vars.
* Priority: LLM_PROVIDER > OPENAI_PROVIDER > auto-detect from keys/endpoints.
*/
function resolveProviderType(): LLMProviderType {
const explicit = (process.env.LLM_PROVIDER || process.env.OPENAI_PROVIDER || '').toLowerCase();
if (explicit === 'azure') return 'azure';
if (explicit === 'openai') return 'openai';
if (explicit === 'perplexity') return 'perplexity';
if (explicit === 'gemini') return 'gemini';
if (explicit === 'fallback') return 'fallback';
if (explicit === 'mock') return 'mock';
// Auto-detect from environment
const azureEndpoint = process.env.AZURE_OPENAI_ENDPOINT ?? '';
const baseUrl = process.env.OPENAI_BASE_URL ?? '';
if (azureEndpoint.trim().length > 0) return 'azure';
if (baseUrl.includes('.cognitive.microsoft.com') || baseUrl.includes('.openai.azure.com'))
return 'azure';
if (process.env.PERPLEXITY_API_KEY) return 'perplexity';
if (process.env.GEMINI_API_KEY) return 'gemini';
return 'openai';
}
/**
* Get the singleton LLM provider.
*/
export function getLLM(): LLMProvider {
if (!_provider) {
_provider = createLLMProvider(resolveProviderType());
}
return _provider;
}
/**
* Create an LLM provider by type.
* For 'fallback', reads LLM_FALLBACK_ORDER env var (comma-separated provider names).
*/
export function createLLMProvider(type: LLMProviderType): LLMProvider {
switch (type) {
case 'azure':
return new AzureOpenAIProvider();
case 'openai':
return new OpenAIProvider();
case 'perplexity':
return new PerplexityProvider();
case 'gemini':
return new GeminiProvider();
case 'fallback': {
const order = (process.env.LLM_FALLBACK_ORDER ?? 'perplexity,openai,gemini')
.split(',')
.map(s => s.trim() as LLMProviderType)
.filter(name => name && name !== 'fallback'); // prevent infinite recursion
if (order.length === 0) {
throw new Error('LLM_FALLBACK_ORDER must contain at least one non-fallback provider');
}
return new FallbackLLMProvider(order.map(createLLMProvider));
}
case 'mock':
return new MockLLMProvider();
default:
throw new Error(
`Unknown LLM_PROVIDER: '${type}'. Valid: azure, openai, perplexity, gemini, fallback, mock`
);
}
}
/**
* Set the singleton LLM provider (for testing).
*/
export function setLLM(provider: LLMProvider): void {
_provider = provider;
}
/**
* @internal
*/
export function _resetLLM(): void {
_provider = null;
}