feat(llm): have OpenAI provider not require API keys (for endpoints like LM Studio)

This commit is contained in:
perf3ct 2025-06-06 19:22:39 +00:00
parent c26b74495c
commit 85cfc8fbd4
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
4 changed files with 30 additions and 19 deletions

View File

@ -44,7 +44,7 @@ export async function validateEmbeddingProviders(validationWarning: HTMLElement)
// Check OpenAI configuration // Check OpenAI configuration
const apiKey = options.get('openaiApiKey'); const apiKey = options.get('openaiApiKey');
if (!apiKey) { if (!apiKey) {
configIssues.push(`OpenAI API key is missing`); configIssues.push(`OpenAI API key is missing (optional for OpenAI-compatible endpoints)`);
} }
} else if (provider === 'anthropic') { } else if (provider === 'anthropic') {
// Check Anthropic configuration // Check Anthropic configuration

View File

@ -66,12 +66,13 @@ async function listModels(req: Request, res: Response) {
const apiKey = await options.getOption('openaiApiKey'); const apiKey = await options.getOption('openaiApiKey');
if (!apiKey) { if (!apiKey) {
throw new Error('OpenAI API key is not configured'); // Log warning but don't throw - some OpenAI-compatible endpoints don't require API keys
log.info('OpenAI API key is not configured when listing models. This may cause issues with official OpenAI endpoints.');
} }
// Initialize OpenAI client with the API key and base URL // Initialize OpenAI client with the API key (or empty string) and base URL
const openai = new OpenAI({ const openai = new OpenAI({
apiKey, apiKey: apiKey || '', // Default to empty string if no API key
baseURL: openaiBaseUrl baseURL: openaiBaseUrl
}); });
@ -84,9 +85,9 @@ async function listModels(req: Request, res: Response) {
// Include all models as chat models, without filtering by specific model names // Include all models as chat models, without filtering by specific model names
// This allows models from providers like OpenRouter to be displayed // This allows models from providers like OpenRouter to be displayed
const chatModels = allModels const chatModels = allModels
.filter((model) => .filter((model) =>
// Exclude models that are explicitly for embeddings // Exclude models that are explicitly for embeddings
!model.id.includes('embedding') && !model.id.includes('embedding') &&
!model.id.includes('embed') !model.id.includes('embed')
) )
.map((model) => ({ .map((model) => ({

View File

@ -14,7 +14,9 @@ export class OpenAIService extends BaseAIService {
} }
override isAvailable(): boolean { override isAvailable(): boolean {
return super.isAvailable() && !!options.getOption('openaiApiKey'); // Make API key optional to support OpenAI-compatible endpoints that don't require authentication
// The provider is considered available as long as the parent checks pass
return super.isAvailable();
} }
private getClient(apiKey: string, baseUrl?: string): OpenAI { private getClient(apiKey: string, baseUrl?: string): OpenAI {
@ -29,7 +31,7 @@ export class OpenAIService extends BaseAIService {
async generateChatCompletion(messages: Message[], opts: ChatCompletionOptions = {}): Promise<ChatResponse> { async generateChatCompletion(messages: Message[], opts: ChatCompletionOptions = {}): Promise<ChatResponse> {
if (!this.isAvailable()) { if (!this.isAvailable()) {
throw new Error('OpenAI service is not available. Check API key and AI settings.'); throw new Error('OpenAI service is not available. Check AI settings.');
} }
// Get provider-specific options from the central provider manager // Get provider-specific options from the central provider manager

View File

@ -134,7 +134,7 @@ export async function createProvidersFromCurrentOptions(): Promise<EmbeddingProv
const ollamaEmbeddingBaseUrl = await options.getOption('ollamaEmbeddingBaseUrl'); const ollamaEmbeddingBaseUrl = await options.getOption('ollamaEmbeddingBaseUrl');
if (ollamaEmbeddingBaseUrl) { if (ollamaEmbeddingBaseUrl) {
const embeddingModel = await options.getOption('ollamaEmbeddingModel'); const embeddingModel = await options.getOption('ollamaEmbeddingModel');
try { try {
const ollamaProvider = new OllamaEmbeddingProvider({ const ollamaProvider = new OllamaEmbeddingProvider({
model: embeddingModel, model: embeddingModel,
@ -152,23 +152,30 @@ export async function createProvidersFromCurrentOptions(): Promise<EmbeddingProv
} }
} }
// Create OpenAI provider if API key is configured // Create OpenAI provider even without API key (for OpenAI-compatible endpoints)
const openaiApiKey = await options.getOption('openaiApiKey'); const openaiApiKey = await options.getOption('openaiApiKey');
if (openaiApiKey) { const openaiBaseUrl = await options.getOption('openaiBaseUrl');
const openaiModel = await options.getOption('openaiEmbeddingModel') || 'text-embedding-3-small';
const openaiBaseUrl = await options.getOption('openaiBaseUrl') || 'https://api.openai.com/v1'; // Only create OpenAI provider if base URL is set or API key is provided
if (openaiApiKey || openaiBaseUrl) {
const openaiModel = await options.getOption('openaiEmbeddingModel')
const finalBaseUrl = openaiBaseUrl || 'https://api.openai.com/v1';
if (!openaiApiKey) {
log.info('Creating OpenAI embedding provider without API key. This may cause issues with official OpenAI endpoints.');
}
const openaiProvider = new OpenAIEmbeddingProvider({ const openaiProvider = new OpenAIEmbeddingProvider({
model: openaiModel, model: openaiModel,
dimension: 1536, dimension: 1536,
type: 'float32', type: 'float32',
apiKey: openaiApiKey, apiKey: openaiApiKey || '', // Default to empty string
baseUrl: openaiBaseUrl baseUrl: finalBaseUrl
}); });
registerEmbeddingProvider(openaiProvider); registerEmbeddingProvider(openaiProvider);
result.push(openaiProvider); result.push(openaiProvider);
log.info(`Created OpenAI provider on-demand: ${openaiModel}`); log.info(`Created OpenAI provider on-demand: ${openaiModel} at ${finalBaseUrl}`);
} }
// Create Voyage provider if API key is configured // Create Voyage provider if API key is configured
@ -221,7 +228,7 @@ export async function getEnabledEmbeddingProviders(): Promise<EmbeddingProvider[
// First try to get existing registered providers // First try to get existing registered providers
const existingProviders = Array.from(providers.values()); const existingProviders = Array.from(providers.values());
// If no providers are registered, create them on-demand from current options // If no providers are registered, create them on-demand from current options
if (existingProviders.length === 0) { if (existingProviders.length === 0) {
log.info('No providers registered, creating from current options'); log.info('No providers registered, creating from current options');
@ -352,7 +359,8 @@ export function getOpenAIOptions(
try { try {
const apiKey = options.getOption('openaiApiKey'); const apiKey = options.getOption('openaiApiKey');
if (!apiKey) { if (!apiKey) {
throw new Error('OpenAI API key is not configured'); // Log warning but don't throw - some OpenAI-compatible endpoints don't require API keys
log.info('OpenAI API key is not configured. This may cause issues with official OpenAI endpoints.');
} }
const baseUrl = options.getOption('openaiBaseUrl') || PROVIDER_CONSTANTS.OPENAI.BASE_URL; const baseUrl = options.getOption('openaiBaseUrl') || PROVIDER_CONSTANTS.OPENAI.BASE_URL;
@ -377,7 +385,7 @@ export function getOpenAIOptions(
return { return {
// Connection settings // Connection settings
apiKey, apiKey: apiKey || '', // Default to empty string if no API key
baseUrl, baseUrl,
// Provider metadata // Provider metadata