From b25b9cd2c35863d8eb4e26cd83af7dd945eee729 Mon Sep 17 00:00:00 2001 From: Celina Hanouti Date: Thu, 24 Jul 2025 15:00:48 +0100 Subject: [PATCH 1/4] add Hugging Face Inference Providers support --- .../byok/vscode-node/byokContribution.ts | 2 + .../byok/vscode-node/byokUIService.ts | 2 +- .../byok/vscode-node/huggingfaceProvider.ts | 120 ++++++++++++++++++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/extension/byok/vscode-node/huggingfaceProvider.ts diff --git a/src/extension/byok/vscode-node/byokContribution.ts b/src/extension/byok/vscode-node/byokContribution.ts index a2784f9e8..f4a34bc0b 100644 --- a/src/extension/byok/vscode-node/byokContribution.ts +++ b/src/extension/byok/vscode-node/byokContribution.ts @@ -22,6 +22,7 @@ import { BYOKUIService, ModelConfig } from './byokUIService'; import { CerebrasModelRegistry } from './cerebrasProvider'; import { GeminiBYOKModelRegistry } from './geminiProvider'; import { GroqModelRegistry } from './groqProvider'; +import { HuggingFaceBYOKModelRegistry } from './huggingfaceProvider'; import { OllamaModelRegistry } from './ollamaProvider'; import { OpenRouterBYOKModelRegistry } from './openRouterProvider'; @@ -77,6 +78,7 @@ export class BYOKContrib extends Disposable implements IExtensionContribution { this._modelRegistries.push(instantiationService.createInstance(OAIBYOKModelRegistry)); this._modelRegistries.push(instantiationService.createInstance(OllamaModelRegistry, this._configurationService.getConfig(ConfigKey.OllamaEndpoint))); this._modelRegistries.push(instantiationService.createInstance(OpenRouterBYOKModelRegistry)); + this._modelRegistries.push(instantiationService.createInstance(HuggingFaceBYOKModelRegistry)); // Update known models list from CDN so all providers have the same list await this.fetchKnownModelList(this._fetcherService); } diff --git a/src/extension/byok/vscode-node/byokUIService.ts b/src/extension/byok/vscode-node/byokUIService.ts index cdb3ffea0..489adfc09 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..36dfdf618 --- /dev/null +++ b/src/extension/byok/vscode-node/huggingfaceProvider.ts @@ -0,0 +1,120 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { IChatModelInformation } from '../../../platform/endpoint/common/endpointProvider'; +import { ILogService } from '../../../platform/log/common/logService'; +import { IFetcherService } from '../../../platform/networking/common/fetcherService'; +import { TokenizerType } from '../../../util/common/tokenizer'; +import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation'; +import { BYOKAuthType } from '../common/byokProvider'; +import { BaseOpenAICompatibleBYOKRegistry } from './baseOpenAICompatibleProvider'; + +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 HuggingFaceBYOKModelRegistry extends BaseOpenAICompatibleBYOKRegistry { + constructor( + @IFetcherService _fetcherService: IFetcherService, + @ILogService _logService: ILogService, + @IInstantiationService _instantiationService: IInstantiationService, + ) { + super( + BYOKAuthType.GlobalApiKey, + 'Hugging Face', + 'https://router.huggingface.co/v1', + _fetcherService, + _logService, + _instantiationService + ); + } + + override async getAllModels(apiKey: string): Promise<{ id: string; name: string }[]> { + const response = await this._fetcherService.fetch('https://router.huggingface.co/v1/models', { method: 'GET' }); + const data: HuggingFaceAPIResponse = await response.json(); + + // Filter models that have at least one provider with supports_tools: true + const toolsSupportedModels = data.data.filter(model => + model.providers.some(provider => provider.supports_tools === true) + ); + + return toolsSupportedModels.map(model => ({ id: model.id, name: model.id })); + } + + private async fetchHuggingFaceModel(modelId: string): Promise { + const response = await this._fetcherService.fetch('https://router.huggingface.co/v1/models', { method: 'GET' }); + const data: HuggingFaceAPIResponse = await response.json(); + const model = data.data.find(m => m.id === modelId); + if (!model) { + throw new Error(`Model ${modelId} not found`); + } + return model; + } + + override async getModelInfo(modelId: string, apiKey: string): Promise { + const model = await this.fetchHuggingFaceModel(modelId); + + // Find the first provider that supports tool calling + const toolsSupportedModel = model.providers.find(provider => provider.supports_tools === true); + if (!toolsSupportedModel) { + throw new Error(`Model ${modelId} does not support tool calling`); + } + + // Hack to to infer vision capabilities from the model name + const isVisionModel = modelId.toLowerCase().includes('vision') || + modelId.toLowerCase().includes('vl'); + + // Use context length if available, otherwise fall back to a default value + const contextWindow = toolsSupportedModel.context_length || 128000; // Default + + // Use the provider name in the model display name + const modelName = `${model.id} (${toolsSupportedModel.provider})`; + + const modelInfo: IChatModelInformation = { + id: model.id, + name: `${this.name}: ${modelName}`, + version: '1.0.0', + capabilities: { + type: 'chat', + family: 'HuggingFace', + supports: { + streaming: true, + vision: isVisionModel, + tool_calls: true, + }, + tokenizer: TokenizerType.O200K, + limits: { + max_context_window_tokens: contextWindow, + max_prompt_tokens: contextWindow, // Use the full context window for max prompt tokens, as the information is not available + max_output_tokens: contextWindow / 2 + } + }, + is_chat_default: false, + is_chat_fallback: false, + model_picker_enabled: true + }; + return modelInfo; + } +} \ No newline at end of file From 546913250c24722dfc5a8f3968ad544b8690abda Mon Sep 17 00:00:00 2001 From: Celina Hanouti Date: Fri, 25 Jul 2025 14:33:56 +0100 Subject: [PATCH 2/4] update hugging face byok implementation --- package.json | 4 +++ .../byok/vscode-node/huggingfaceProvider.ts | 30 +++++++++++-------- 2 files changed, 21 insertions(+), 13 deletions(-) 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/huggingfaceProvider.ts b/src/extension/byok/vscode-node/huggingfaceProvider.ts index bce5ff33c..bf049dfcc 100644 --- a/src/extension/byok/vscode-node/huggingfaceProvider.ts +++ b/src/extension/byok/vscode-node/huggingfaceProvider.ts @@ -36,7 +36,7 @@ interface HuggingFaceModel { export class HuggingFaceBYOKLMProvider extends BaseOpenAICompatibleLMProvider { - public static readonly providerName = 'HuggingFace'; + public static readonly providerName = 'Hugging Face'; constructor( byokStorageService: IBYOKStorageService, @IFetcherService _fetcherService: IFetcherService, @@ -60,24 +60,29 @@ export class HuggingFaceBYOKLMProvider extends BaseOpenAICompatibleLMProvider { const response = await this._fetcherService.fetch('https://router.huggingface.co/v1/models', { method: 'GET' }); const data: HuggingFaceAPIResponse = await response.json(); const knownModels: BYOKKnownModels = {}; - // Filter models that have at least one provider with supports_tools set to true - const toolsSupportedModels = data.data.filter(model => - model.providers.some(provider => provider.supports_tools === true) - ); - for (const model of toolsSupportedModels) { - const toolsSupportedProvider = model.providers.find(provider => provider.supports_tools === true); + + for (const model of data.data) { + const toolsSupportedProvider = model.providers.find(provider => provider.supports_tools === true && provider.status === 'live'); if (!toolsSupportedProvider) { - continue; // Skip if no provider supports tools + continue; + } + + let vision = false; + try { + const modelInfoResp = await this._fetcherService.fetch(`https://huggingface.co/api/models/${model.id}`, { method: 'GET' }); + const modelInfo = await modelInfoResp.json(); + vision = modelInfo.pipeline_tag === 'image-text-to-text'; + } catch (err) { + this._logService.logger.warn(`Failed to fetch vision capabilities for model ${model.id}: ${err}`); } - // Use the provider name and context length from the tools supported provider + const modelName = `${model.id} (${toolsSupportedProvider.provider})`; knownModels[model.id] = { name: modelName, toolCalling: true, - vision: model.id.toLowerCase().includes('vision') || - model.id.toLowerCase().includes('vl'), + vision, maxInputTokens: (toolsSupportedProvider.context_length || 128000) - 16000, - maxOutputTokens: 16000 + maxOutputTokens: 16000, }; } return knownModels; @@ -85,7 +90,6 @@ export class HuggingFaceBYOKLMProvider extends BaseOpenAICompatibleLMProvider { this._logService.logger.error(error, `Error fetching available Hugging Face models`); throw error; } - } } From e19d6ef7969cd8297529e28f99fdf8fc5e9fa9bd Mon Sep 17 00:00:00 2001 From: Celina Hanouti Date: Fri, 25 Jul 2025 15:21:09 +0100 Subject: [PATCH 3/4] fetch models information in batch --- .../byok/vscode-node/huggingfaceProvider.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/extension/byok/vscode-node/huggingfaceProvider.ts b/src/extension/byok/vscode-node/huggingfaceProvider.ts index bf049dfcc..4ca8526e0 100644 --- a/src/extension/byok/vscode-node/huggingfaceProvider.ts +++ b/src/extension/byok/vscode-node/huggingfaceProvider.ts @@ -61,20 +61,16 @@ export class HuggingFaceBYOKLMProvider extends BaseOpenAICompatibleLMProvider { 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 && modelInfo.pipeline_tag === 'image-text-to-text' ? true : false; - let vision = false; - try { - const modelInfoResp = await this._fetcherService.fetch(`https://huggingface.co/api/models/${model.id}`, { method: 'GET' }); - const modelInfo = await modelInfoResp.json(); - vision = modelInfo.pipeline_tag === 'image-text-to-text'; - } catch (err) { - this._logService.logger.warn(`Failed to fetch vision capabilities for model ${model.id}: ${err}`); - } const modelName = `${model.id} (${toolsSupportedProvider.provider})`; knownModels[model.id] = { From 6acdae1dfd650ca2a4cc25aea8816dd5f2a3acf1 Mon Sep 17 00:00:00 2001 From: Celina Hanouti Date: Fri, 25 Jul 2025 15:53:38 +0100 Subject: [PATCH 4/4] nit --- src/extension/byok/vscode-node/huggingfaceProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension/byok/vscode-node/huggingfaceProvider.ts b/src/extension/byok/vscode-node/huggingfaceProvider.ts index 4ca8526e0..d0ed9c983 100644 --- a/src/extension/byok/vscode-node/huggingfaceProvider.ts +++ b/src/extension/byok/vscode-node/huggingfaceProvider.ts @@ -69,7 +69,7 @@ export class HuggingFaceBYOKLMProvider extends BaseOpenAICompatibleLMProvider { continue; } const modelInfo = modelInfos.find((info: any) => info.id === model.id); - const vision = modelInfo && modelInfo.pipeline_tag === 'image-text-to-text' ? true : false; + const vision = modelInfo?.pipeline_tag === 'image-text-to-text'; const modelName = `${model.id} (${toolsSupportedProvider.provider})`;