diff --git a/package.json b/package.json index 807526bd4..48318cee9 100644 --- a/package.json +++ b/package.json @@ -1569,6 +1569,10 @@ "vendor": "groq", "displayName": "Groq" }, + { + "vendor": "hugging face", + "displayName": "Hugging Face" + }, { "vendor": "openrouter", "displayName": "OpenRouter" diff --git a/src/extension/byok/vscode-node/byokContribution.ts b/src/extension/byok/vscode-node/byokContribution.ts index d68605009..c4dd89600 100644 --- a/src/extension/byok/vscode-node/byokContribution.ts +++ b/src/extension/byok/vscode-node/byokContribution.ts @@ -18,6 +18,7 @@ import { AzureBYOKModelProvider } from './azureProvider'; import { BYOKStorageService, IBYOKStorageService } from './byokStorageService'; import { GeminiBYOKLMProvider } from './geminiProvider'; import { GroqBYOKLMProvider } from './groqProvider'; +import { HuggingFaceBYOKLMProvider } from './huggingfaceProvider'; import { OllamaLMProvider } from './ollamaProvider'; import { OAIBYOKLMProvider } from './openAIProvider'; import { OpenRouterLMProvider } from './openRouterProvider'; @@ -52,6 +53,7 @@ export class BYOKContrib extends Disposable implements IExtensionContribution { this._store.add(lm.registerChatModelProvider(AnthropicLMProvider.providerName.toLowerCase(), this._instantiationService.createInstance(AnthropicLMProvider, knownModels[AnthropicLMProvider.providerName], this._byokStorageService))); this._store.add(lm.registerChatModelProvider(GroqBYOKLMProvider.providerName.toLowerCase(), this._instantiationService.createInstance(GroqBYOKLMProvider, knownModels[GroqBYOKLMProvider.providerName], this._byokStorageService))); this._store.add(lm.registerChatModelProvider(GeminiBYOKLMProvider.providerName.toLowerCase(), this._instantiationService.createInstance(GeminiBYOKLMProvider, knownModels[GeminiBYOKLMProvider.providerName], this._byokStorageService))); + this._store.add(lm.registerChatModelProvider(HuggingFaceBYOKLMProvider.providerName.toLowerCase(), this._instantiationService.createInstance(HuggingFaceBYOKLMProvider, this._byokStorageService))); this._store.add(lm.registerChatModelProvider(OAIBYOKLMProvider.providerName.toLowerCase(), this._instantiationService.createInstance(OAIBYOKLMProvider, knownModels[OAIBYOKLMProvider.providerName], this._byokStorageService))); this._store.add(lm.registerChatModelProvider(OpenRouterLMProvider.providerName.toLowerCase(), this._instantiationService.createInstance(OpenRouterLMProvider, this._byokStorageService))); this._store.add(lm.registerChatModelProvider('azure', this._instantiationService.createInstance(AzureBYOKModelProvider, this._byokStorageService))); diff --git a/src/extension/byok/vscode-node/byokUIService.ts b/src/extension/byok/vscode-node/byokUIService.ts index bcee58a74..546d2a441 100644 --- a/src/extension/byok/vscode-node/byokUIService.ts +++ b/src/extension/byok/vscode-node/byokUIService.ts @@ -557,7 +557,7 @@ export class BYOKUIService { // others go to advanced config to ask the user for info if (state.selectedProviderRegistry.authType === BYOKAuthType.PerModelDeployment) { return { nextStep: ConfigurationStep.DeploymentUrl }; - } else if (state.selectedProviderRegistry.name === 'OpenRouter') { + } else if (state.selectedProviderRegistry.name === 'OpenRouter' || state.selectedProviderRegistry.name === 'Hugging Face') { return { nextStep: ConfigurationStep.Complete }; } else { return { nextStep: ConfigurationStep.AdvancedConfig }; diff --git a/src/extension/byok/vscode-node/huggingfaceProvider.ts b/src/extension/byok/vscode-node/huggingfaceProvider.ts new file mode 100644 index 000000000..d0ed9c983 --- /dev/null +++ b/src/extension/byok/vscode-node/huggingfaceProvider.ts @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { ILogService } from '../../../platform/log/common/logService'; +import { IFetcherService } from '../../../platform/networking/common/fetcherService'; +import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation'; +import { BYOKAuthType, BYOKKnownModels } from '../common/byokProvider'; +import { BaseOpenAICompatibleLMProvider } from './baseOpenAICompatibleProvider'; +import { IBYOKStorageService } from './byokStorageService'; + +interface HuggingFaceAPIResponse { + object: string; + data: HuggingFaceModel[]; +} + +interface HuggingFaceProvider { + provider: string; + status: string; + supports_tools?: boolean; + supports_structured_output?: boolean; + context_length?: number; + pricing?: { + input: number; + output: number; + }; +} + +interface HuggingFaceModel { + id: string; + object: string; + created: number; + owned_by: string; + providers: HuggingFaceProvider[]; +} + + +export class HuggingFaceBYOKLMProvider extends BaseOpenAICompatibleLMProvider { + public static readonly providerName = 'Hugging Face'; + constructor( + byokStorageService: IBYOKStorageService, + @IFetcherService _fetcherService: IFetcherService, + @ILogService _logService: ILogService, + @IInstantiationService _instantiationService: IInstantiationService, + ) { + super( + BYOKAuthType.GlobalApiKey, + HuggingFaceBYOKLMProvider.providerName, + 'https://router.huggingface.co/v1', + undefined, + byokStorageService, + _fetcherService, + _logService, + _instantiationService + ); + } + + protected override async getAllModels(): Promise { + try { + const response = await this._fetcherService.fetch('https://router.huggingface.co/v1/models', { method: 'GET' }); + const data: HuggingFaceAPIResponse = await response.json(); + const knownModels: BYOKKnownModels = {}; + + const modelInfoResp = await this._fetcherService.fetch(`https://huggingface.co/api/models?other=conversational&inference=warm`, { method: 'GET' }); + const modelInfos = await modelInfoResp.json(); + for (const model of data.data) { + const toolsSupportedProvider = model.providers.find(provider => provider.supports_tools === true && provider.status === 'live'); + if (!toolsSupportedProvider) { + continue; + } + const modelInfo = modelInfos.find((info: any) => info.id === model.id); + const vision = modelInfo?.pipeline_tag === 'image-text-to-text'; + + + const modelName = `${model.id} (${toolsSupportedProvider.provider})`; + knownModels[model.id] = { + name: modelName, + toolCalling: true, + vision, + maxInputTokens: (toolsSupportedProvider.context_length || 128000) - 16000, + maxOutputTokens: 16000, + }; + } + return knownModels; + } catch (error) { + this._logService.logger.error(error, `Error fetching available Hugging Face models`); + throw error; + } + } +} +