Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/byok/vscode-node/ollamaProvider.ts
13399 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
6
import { IChatModelInformation } from '../../../platform/endpoint/common/endpointProvider';
7
import { ILogService } from '../../../platform/log/common/logService';
8
import { IFetcherService } from '../../../platform/networking/common/fetcherService';
9
import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';
10
import { ErrorUtils } from '../../../util/common/errors';
11
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
12
import { byokKnownModelsToAPIInfo, resolveModelInfo } from '../common/byokProvider';
13
import { OpenAIEndpoint } from '../node/openAIEndpoint';
14
import { AbstractOpenAICompatibleLMProvider, LanguageModelChatConfiguration, OpenAICompatibleLanguageModelChatInformation } from './abstractLanguageModelChatProvider';
15
import { IBYOKStorageService } from './byokStorageService';
16
17
interface OllamaModelInfoAPIResponse {
18
template: string;
19
capabilities: string[];
20
details: { family: string };
21
remote_model?: string;
22
model_info?: {
23
'general.basename': string;
24
'general.architecture': string;
25
[other: string]: any;
26
};
27
}
28
29
interface OllamaVersionResponse {
30
version: string;
31
}
32
33
// Minimum supported Ollama version - versions below this may have compatibility issues
34
const MINIMUM_OLLAMA_VERSION = '0.6.4';
35
36
export interface OllamaConfig extends LanguageModelChatConfiguration {
37
url: string;
38
}
39
40
export class OllamaLMProvider extends AbstractOpenAICompatibleLMProvider<OllamaConfig> {
41
public static readonly providerName = 'Ollama';
42
private _modelCache = new Map<string, IChatModelInformation>();
43
44
constructor(
45
byokStorageService: IBYOKStorageService,
46
@IFetcherService fetcherService: IFetcherService,
47
@IConfigurationService configurationService: IConfigurationService,
48
@ILogService logService: ILogService,
49
@IInstantiationService instantiationService: IInstantiationService,
50
@IExperimentationService expService: IExperimentationService
51
) {
52
super(
53
OllamaLMProvider.providerName.toLowerCase(),
54
OllamaLMProvider.providerName,
55
undefined,
56
byokStorageService,
57
fetcherService,
58
logService,
59
instantiationService,
60
configurationService,
61
expService
62
);
63
64
this.migrateConfig();
65
}
66
67
private async migrateConfig(): Promise<void> {
68
const baseUrl = this.getBaseUrlFromSettings();
69
if (!baseUrl) {
70
return;
71
}
72
await this.configureDefaultGroupIfExists(this._name, { url: baseUrl });
73
await this._configurationService.setConfig(ConfigKey.Deprecated.OllamaEndpoint, undefined);
74
}
75
76
private getBaseUrlFromSettings(): string | undefined {
77
if (this._configurationService.isConfigured(ConfigKey.Deprecated.OllamaEndpoint)) {
78
return this._configurationService.getConfig(ConfigKey.Deprecated.OllamaEndpoint);
79
}
80
return undefined;
81
}
82
83
protected override async getAllModels(silent: boolean, apiKey: string | undefined, config: OllamaConfig | undefined): Promise<OpenAICompatibleLanguageModelChatInformation<OllamaConfig>[]> {
84
if (!config) {
85
return [];
86
}
87
88
const ollamaBaseUrl = config.url;
89
90
try {
91
// Check Ollama server version before proceeding with model operations
92
await this._checkOllamaVersion(ollamaBaseUrl);
93
94
const response = await this._fetcherService.fetch(`${ollamaBaseUrl}/api/tags`, { method: 'GET', callSite: 'ollama-tags' });
95
const models = (await response.json()).models;
96
this._knownModels = {};
97
for (const model of models) {
98
let modelInfo = this._modelCache.get(`${ollamaBaseUrl}/${model.model}`);
99
if (!modelInfo) {
100
try {
101
modelInfo = await this._getOllamaModelInfo(ollamaBaseUrl, model.model);
102
} catch (e) {
103
const error = ErrorUtils.fromUnknown(e);
104
this._logService.error(error, 'ollamaProvider: failed to fetch Ollama model info');
105
this._logService.debug(`[ollamaProvider] Failed model info fetch for model=${model.model}`);
106
continue; // Skip this model but continue processing others
107
}
108
this._modelCache.set(`${ollamaBaseUrl}/${model.model}`, modelInfo);
109
}
110
this._knownModels[modelInfo.id] = {
111
maxInputTokens: modelInfo.capabilities.limits?.max_prompt_tokens ?? 4096,
112
maxOutputTokens: modelInfo.capabilities.limits?.max_output_tokens ?? 4096,
113
name: modelInfo.name,
114
toolCalling: !!modelInfo.capabilities.supports.tool_calls,
115
vision: !!modelInfo.capabilities.supports.vision
116
};
117
}
118
119
return byokKnownModelsToAPIInfo(this._name, this._knownModels).map(model => ({
120
...model,
121
url: ollamaBaseUrl
122
}));
123
124
} catch (e) {
125
// Check if this is our version check error and preserve it
126
if (e instanceof Error && e.message.includes('Ollama server version')) {
127
throw e;
128
}
129
throw new Error('Failed to fetch models from Ollama. Please ensure Ollama is running. If ollama is on another host, please configure the `"github.copilot.chat.byok.ollamaEndpoint"` setting.');
130
}
131
}
132
133
protected override getModelsBaseUrl(configuration: OllamaConfig | undefined): string {
134
return configuration?.url ?? 'http://localhost:11434';
135
}
136
137
protected override async createOpenAIEndPoint(model: OpenAICompatibleLanguageModelChatInformation<OllamaConfig>): Promise<OpenAIEndpoint> {
138
const modelInfo = this.getModelInfo(model.id, model.url);
139
const url = `${model.url}/v1/chat/completions`;
140
return this._instantiationService.createInstance(OpenAIEndpoint, modelInfo, model.configuration?.apiKey ?? '', url);
141
}
142
143
private async _getOllamaModelInfo(ollamaBaseUrl: string, modelId: string): Promise<IChatModelInformation> {
144
const modelInfo = await this._fetchOllamaModelInformation(ollamaBaseUrl, modelId);
145
const contextWindow = modelInfo?.model_info?.[`${modelInfo.model_info['general.architecture']}.context_length`] ?? 32768;
146
const outputTokens = contextWindow < 4096 ? Math.floor(contextWindow / 2) : 4096;
147
const modelCapabilities = {
148
name: modelInfo?.model_info?.['general.basename'] ?? modelInfo.remote_model ?? modelId,
149
maxOutputTokens: outputTokens,
150
maxInputTokens: contextWindow - outputTokens,
151
vision: modelInfo.capabilities.includes('vision'),
152
toolCalling: modelInfo.capabilities.includes('tools')
153
};
154
155
return resolveModelInfo(modelId, this._name, this._knownModels, modelCapabilities);
156
}
157
158
/**
159
* Compare version strings to check if current version meets minimum requirements
160
* @param currentVersion Current Ollama server version
161
* @returns true if version is supported, false otherwise
162
*/
163
private _isVersionSupported(currentVersion: string): boolean {
164
if (currentVersion === '0.0.0') {
165
// allow all dev versions through
166
return true;
167
}
168
169
// Simple version comparison: split by dots and compare numerically
170
const currentParts = currentVersion.split('.').map(n => parseInt(n, 10));
171
const minimumParts = MINIMUM_OLLAMA_VERSION.split('.').map(n => parseInt(n, 10));
172
173
for (let i = 0; i < Math.max(currentParts.length, minimumParts.length); i++) {
174
const current = currentParts[i] || 0;
175
const minimum = minimumParts[i] || 0;
176
177
if (current > minimum) {
178
return true;
179
}
180
if (current < minimum) {
181
return false;
182
}
183
}
184
185
return true; // versions are equal
186
}
187
188
private async _fetchOllamaModelInformation(ollamaBaseUrl: string, modelId: string): Promise<OllamaModelInfoAPIResponse> {
189
const response = await this._fetcherService.fetch(`${ollamaBaseUrl}/api/show`, {
190
method: 'POST',
191
callSite: 'ollama-show',
192
headers: {
193
'Content-Type': 'application/json'
194
},
195
body: JSON.stringify({ model: modelId })
196
});
197
return response.json() as unknown as OllamaModelInfoAPIResponse;
198
}
199
/**
200
* Check if the connected Ollama server version meets the minimum requirements
201
* @throws Error if version is below minimum or version check fails
202
*/
203
private async _checkOllamaVersion(ollamaBaseUrl: string): Promise<void> {
204
try {
205
const response = await this._fetcherService.fetch(`${ollamaBaseUrl}/api/version`, { method: 'GET', callSite: 'ollama-version' });
206
const versionInfo = await response.json() as OllamaVersionResponse;
207
208
if (!this._isVersionSupported(versionInfo.version)) {
209
throw new Error(
210
`Ollama server version ${versionInfo.version} is not supported. ` +
211
`Please upgrade to version ${MINIMUM_OLLAMA_VERSION} or higher. ` +
212
`Visit https://ollama.ai for upgrade instructions.`
213
);
214
}
215
} catch (e) {
216
if (e instanceof Error && e.message.includes('Ollama server version')) {
217
// Re-throw our custom version error
218
throw e;
219
}
220
// If version endpoint fails
221
throw new Error(
222
`Unable to verify Ollama server version. Please ensure you have Ollama version ${MINIMUM_OLLAMA_VERSION} or higher installed. ` +
223
`If you're running an older version, please upgrade from https://ollama.ai`
224
);
225
}
226
}
227
}
228