From 1a32b061cc2339f5faed9505df2e65f8b4fad4c4 Mon Sep 17 00:00:00 2001 From: Dennis Huebner Date: Fri, 16 Aug 2024 13:34:04 +0200 Subject: [PATCH] feat: Ollama integration for Theia AI Integrates Ollama language models into Theia via the new 'ai-ollama' package. The endpoint and models can be configured via the preferences. --- examples/browser-only/package.json | 1 + examples/browser-only/tsconfig.json | 3 + examples/browser/package.json | 1 + examples/browser/tsconfig.json | 3 + examples/electron/package.json | 1 + examples/electron/tsconfig.json | 3 + packages/ai-ollama/.eslintrc.js | 10 ++ packages/ai-ollama/README.md | 30 ++++ packages/ai-ollama/package.json | 53 ++++++ ...llama-frontend-application-contribution.ts | 59 +++++++ .../src/browser/ollama-frontend-module.ts | 31 ++++ .../src/browser/ollama-preferences.ts | 41 +++++ packages/ai-ollama/src/common/index.ts | 16 ++ .../common/ollama-language-models-manager.ts | 23 +++ .../src/node/ollama-backend-module.ts | 30 ++++ .../src/node/ollama-language-model.ts | 155 ++++++++++++++++++ .../ollama-language-models-manager-impl.ts | 58 +++++++ packages/ai-ollama/src/package.spec.ts | 27 +++ packages/ai-ollama/tsconfig.json | 25 +++ tsconfig.json | 3 + yarn.lock | 134 +++++++++++++-- 21 files changed, 696 insertions(+), 11 deletions(-) create mode 100644 packages/ai-ollama/.eslintrc.js create mode 100644 packages/ai-ollama/README.md create mode 100644 packages/ai-ollama/package.json create mode 100644 packages/ai-ollama/src/browser/ollama-frontend-application-contribution.ts create mode 100644 packages/ai-ollama/src/browser/ollama-frontend-module.ts create mode 100644 packages/ai-ollama/src/browser/ollama-preferences.ts create mode 100644 packages/ai-ollama/src/common/index.ts create mode 100644 packages/ai-ollama/src/common/ollama-language-models-manager.ts create mode 100644 packages/ai-ollama/src/node/ollama-backend-module.ts create mode 100644 packages/ai-ollama/src/node/ollama-language-model.ts create mode 100644 packages/ai-ollama/src/node/ollama-language-models-manager-impl.ts create mode 100644 packages/ai-ollama/src/package.spec.ts create mode 100644 packages/ai-ollama/tsconfig.json diff --git a/examples/browser-only/package.json b/examples/browser-only/package.json index e1ed7399bd982..bec4142e20b8c 100644 --- a/examples/browser-only/package.json +++ b/examples/browser-only/package.json @@ -21,6 +21,7 @@ "@theia/ai-core": "1.53.0", "@theia/ai-history": "1.53.0", "@theia/ai-openai": "1.53.0", + "@theia/ai-ollama": "1.53.0", "@theia/api-samples": "1.53.0", "@theia/bulk-edit": "1.53.0", "@theia/callhierarchy": "1.53.0", diff --git a/examples/browser-only/tsconfig.json b/examples/browser-only/tsconfig.json index 2d8d29a0d1fa1..2c35a886f30d0 100644 --- a/examples/browser-only/tsconfig.json +++ b/examples/browser-only/tsconfig.json @@ -23,6 +23,9 @@ { "path": "../../packages/ai-history" }, + { + "path": "../../packages/ai-ollama" + }, { "path": "../../packages/ai-openai" }, diff --git a/examples/browser/package.json b/examples/browser/package.json index e6b387674d0b7..21ce9dccb8410 100644 --- a/examples/browser/package.json +++ b/examples/browser/package.json @@ -26,6 +26,7 @@ "@theia/ai-core": "1.53.0", "@theia/ai-history": "1.53.0", "@theia/ai-openai": "1.53.0", + "@theia/ai-ollama": "1.53.0", "@theia/ai-terminal": "1.53.0", "@theia/ai-workspace-agent": "1.53.0", "@theia/api-provider-sample": "1.53.0", diff --git a/examples/browser/tsconfig.json b/examples/browser/tsconfig.json index ed5f00cd84c77..f6b8cad219535 100644 --- a/examples/browser/tsconfig.json +++ b/examples/browser/tsconfig.json @@ -23,6 +23,9 @@ { "path": "../../packages/ai-history" }, + { + "path": "../../packages/ai-ollama" + }, { "path": "../../packages/ai-openai" }, diff --git a/examples/electron/package.json b/examples/electron/package.json index 16c72e673eef9..6080cd438088e 100644 --- a/examples/electron/package.json +++ b/examples/electron/package.json @@ -32,6 +32,7 @@ "@theia/ai-core": "1.53.0", "@theia/ai-history": "1.53.0", "@theia/ai-openai": "1.53.0", + "@theia/ai-ollama": "1.53.0", "@theia/ai-terminal": "1.53.0", "@theia/ai-workspace-agent": "1.53.0", "@theia/api-provider-sample": "1.53.0", diff --git a/examples/electron/tsconfig.json b/examples/electron/tsconfig.json index b67a624574045..e36e0e73f4457 100644 --- a/examples/electron/tsconfig.json +++ b/examples/electron/tsconfig.json @@ -26,6 +26,9 @@ { "path": "../../packages/ai-history" }, + { + "path": "../../packages/ai-ollama" + }, { "path": "../../packages/ai-openai" }, diff --git a/packages/ai-ollama/.eslintrc.js b/packages/ai-ollama/.eslintrc.js new file mode 100644 index 0000000000000..13089943582b6 --- /dev/null +++ b/packages/ai-ollama/.eslintrc.js @@ -0,0 +1,10 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + extends: [ + '../../configs/build.eslintrc.json' + ], + parserOptions: { + tsconfigRootDir: __dirname, + project: 'tsconfig.json' + } +}; diff --git a/packages/ai-ollama/README.md b/packages/ai-ollama/README.md new file mode 100644 index 0000000000000..f9eba7a29e173 --- /dev/null +++ b/packages/ai-ollama/README.md @@ -0,0 +1,30 @@ +
+ +
+ +theia-ext-logo + +

ECLIPSE THEIA - Ollama EXTENSION

+ +
+ +
+ +## Description + +The `@theia/ai-ollama` integrates Ollama's models with Theia AI. + +## Additional Information + +- [Theia - GitHub](https://github.com/eclipse-theia/theia) +- [Theia - Website](https://theia-ide.org/) + +## License + +- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/) +- [(Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp) + +## Trademark + +"Theia" is a trademark of the Eclipse Foundation + diff --git a/packages/ai-ollama/package.json b/packages/ai-ollama/package.json new file mode 100644 index 0000000000000..779515df07d0b --- /dev/null +++ b/packages/ai-ollama/package.json @@ -0,0 +1,53 @@ +{ + "name": "@theia/ai-ollama", + "version": "1.53.0", + "description": "Theia - Ollama Integration", + "dependencies": { + "@theia/core": "1.53.0", + "@theia/filesystem": "1.53.0", + "@theia/workspace": "1.53.0", + "minimatch": "^5.1.0", + "tslib": "^2.6.2", + "ollama": "^0.5.8", + "@theia/ai-core": "1.53.0" + }, + "publishConfig": { + "access": "public" + }, + "theiaExtensions": [ + { + "frontend": "lib/browser/ollama-frontend-module", + "backend": "lib/node/ollama-backend-module" + } + ], + "keywords": [ + "theia-extension" + ], + "license": "EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-theia/theia.git" + }, + "bugs": { + "url": "https://github.com/eclipse-theia/theia/issues" + }, + "homepage": "https://github.com/eclipse-theia/theia", + "files": [ + "lib", + "src" + ], + "scripts": { + "build": "theiaext build", + "clean": "theiaext clean", + "compile": "theiaext compile", + "lint": "theiaext lint", + "test": "theiaext test", + "watch": "theiaext watch" + }, + "devDependencies": { + "@theia/ext-scripts": "1.53.0" + }, + "nyc": { + "extends": "../../configs/nyc.json" + } +} diff --git a/packages/ai-ollama/src/browser/ollama-frontend-application-contribution.ts b/packages/ai-ollama/src/browser/ollama-frontend-application-contribution.ts new file mode 100644 index 0000000000000..e08680563cd72 --- /dev/null +++ b/packages/ai-ollama/src/browser/ollama-frontend-application-contribution.ts @@ -0,0 +1,59 @@ +// ***************************************************************************** +// Copyright (C) 2024 TypeFox GmbH. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { FrontendApplicationContribution, PreferenceService } from '@theia/core/lib/browser'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import { OllamaLanguageModelsManager } from '../common'; +import { HOST_PREF, MODELS_PREF } from './ollama-preferences'; + +@injectable() +export class OllamaFrontendApplicationContribution implements FrontendApplicationContribution { + + @inject(PreferenceService) + protected preferenceService: PreferenceService; + + @inject(OllamaLanguageModelsManager) + protected manager: OllamaLanguageModelsManager; + + protected prevModels: string[] = []; + + onStart(): void { + this.preferenceService.ready.then(() => { + const host = this.preferenceService.get(HOST_PREF, 'http://localhost:11434'); + this.manager.setHost(host); + + const models = this.preferenceService.get(MODELS_PREF, []); + this.manager.createLanguageModels(...models); + this.prevModels = [...models]; + + this.preferenceService.onPreferenceChanged(event => { + if (event.preferenceName === HOST_PREF) { + this.manager.setHost(event.newValue); + } else if (event.preferenceName === MODELS_PREF) { + const oldModels = new Set(this.prevModels); + const newModels = new Set(event.newValue as string[]); + + const modelsToRemove = [...oldModels].filter(model => !newModels.has(model)); + const modelsToAdd = [...newModels].filter(model => !oldModels.has(model)); + + this.manager.removeLanguageModels(...modelsToRemove); + this.manager.createLanguageModels(...modelsToAdd); + this.prevModels = [...event.newValue]; + } + }); + }); + } +} diff --git a/packages/ai-ollama/src/browser/ollama-frontend-module.ts b/packages/ai-ollama/src/browser/ollama-frontend-module.ts new file mode 100644 index 0000000000000..7228aba4b4332 --- /dev/null +++ b/packages/ai-ollama/src/browser/ollama-frontend-module.ts @@ -0,0 +1,31 @@ +// ***************************************************************************** +// Copyright (C) 2024 TypeFox GmbH. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { ContainerModule } from '@theia/core/shared/inversify'; +import { OllamaPreferencesSchema } from './ollama-preferences'; +import { FrontendApplicationContribution, PreferenceContribution, RemoteConnectionProvider, ServiceConnectionProvider } from '@theia/core/lib/browser'; +import { OllamaFrontendApplicationContribution } from './ollama-frontend-application-contribution'; +import { OLLAMA_LANGUAGE_MODELS_MANAGER_PATH, OllamaLanguageModelsManager } from '../common'; + +export default new ContainerModule(bind => { + bind(PreferenceContribution).toConstantValue({ schema: OllamaPreferencesSchema }); + bind(OllamaFrontendApplicationContribution).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(OllamaFrontendApplicationContribution); + bind(OllamaLanguageModelsManager).toDynamicValue(ctx => { + const provider = ctx.container.get(RemoteConnectionProvider); + return provider.createProxy(OLLAMA_LANGUAGE_MODELS_MANAGER_PATH); + }).inSingletonScope(); +}); diff --git a/packages/ai-ollama/src/browser/ollama-preferences.ts b/packages/ai-ollama/src/browser/ollama-preferences.ts new file mode 100644 index 0000000000000..b7088001bccc0 --- /dev/null +++ b/packages/ai-ollama/src/browser/ollama-preferences.ts @@ -0,0 +1,41 @@ +// ***************************************************************************** +// Copyright (C) 2024 TypeFox GmbH. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { PreferenceSchema } from '@theia/core/lib/browser/preferences/preference-contribution'; +import { AI_CORE_PREFERENCES_TITLE } from '@theia/ai-core/lib/browser/ai-core-preferences'; + +export const HOST_PREF = 'ai-features.ollama.host'; +export const MODELS_PREF = 'ai-features.ollama.models'; + +export const OllamaPreferencesSchema: PreferenceSchema = { + type: 'object', + properties: { + [HOST_PREF]: { + type: 'string', + title: AI_CORE_PREFERENCES_TITLE, + description: 'Ollama Host', + default: 'http://localhost:11434' + }, + [MODELS_PREF]: { + type: 'array', + title: AI_CORE_PREFERENCES_TITLE, + default: ['llama3', 'gemma2'], + items: { + type: 'string' + } + } + } +}; diff --git a/packages/ai-ollama/src/common/index.ts b/packages/ai-ollama/src/common/index.ts new file mode 100644 index 0000000000000..8d6821f9cc79e --- /dev/null +++ b/packages/ai-ollama/src/common/index.ts @@ -0,0 +1,16 @@ +// ***************************************************************************** +// Copyright (C) 2024 TypeFox GmbH. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** +export * from './ollama-language-models-manager'; diff --git a/packages/ai-ollama/src/common/ollama-language-models-manager.ts b/packages/ai-ollama/src/common/ollama-language-models-manager.ts new file mode 100644 index 0000000000000..2714ef3a7757a --- /dev/null +++ b/packages/ai-ollama/src/common/ollama-language-models-manager.ts @@ -0,0 +1,23 @@ +// ***************************************************************************** +// Copyright (C) 2024 TypeFox GmbH. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** +export const OLLAMA_LANGUAGE_MODELS_MANAGER_PATH = '/services/ollama/language-model-manager'; +export const OllamaLanguageModelsManager = Symbol('OllamaLanguageModelsManager'); +export interface OllamaLanguageModelsManager { + host: string | undefined; + setHost(host: string | undefined): void; + createLanguageModels(...modelIds: string[]): Promise; + removeLanguageModels(...modelIds: string[]): void +} diff --git a/packages/ai-ollama/src/node/ollama-backend-module.ts b/packages/ai-ollama/src/node/ollama-backend-module.ts new file mode 100644 index 0000000000000..667729c7fc4e2 --- /dev/null +++ b/packages/ai-ollama/src/node/ollama-backend-module.ts @@ -0,0 +1,30 @@ +// ***************************************************************************** +// Copyright (C) 2024 TypeFox GmbH. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { ContainerModule } from '@theia/core/shared/inversify'; +import { OLLAMA_LANGUAGE_MODELS_MANAGER_PATH, OllamaLanguageModelsManager } from '../common/ollama-language-models-manager'; +import { ConnectionHandler, RpcConnectionHandler } from '@theia/core'; +import { OllamaLanguageModelsManagerImpl } from './ollama-language-models-manager-impl'; + +export const OllamaModelFactory = Symbol('OllamaModelFactory'); + +export default new ContainerModule(bind => { + bind(OllamaLanguageModelsManagerImpl).toSelf().inSingletonScope(); + bind(OllamaLanguageModelsManager).toService(OllamaLanguageModelsManagerImpl); + bind(ConnectionHandler).toDynamicValue(ctx => + new RpcConnectionHandler(OLLAMA_LANGUAGE_MODELS_MANAGER_PATH, () => ctx.container.get(OllamaLanguageModelsManager)) + ).inSingletonScope(); +}); diff --git a/packages/ai-ollama/src/node/ollama-language-model.ts b/packages/ai-ollama/src/node/ollama-language-model.ts new file mode 100644 index 0000000000000..899305831f7b5 --- /dev/null +++ b/packages/ai-ollama/src/node/ollama-language-model.ts @@ -0,0 +1,155 @@ +// ***************************************************************************** +// Copyright (C) 2024 TypeFox GmbH. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { + LanguageModel, + LanguageModelParsedResponse, + LanguageModelRequest, + LanguageModelRequestMessage, + LanguageModelResponse, + LanguageModelStreamResponsePart, + ToolRequest +} from '@theia/ai-core'; +import { CancellationToken } from '@theia/core'; +import { ChatRequest, ChatResponse, Message, Ollama, Tool } from 'ollama'; + +export const OllamaModelIdentifier = Symbol('OllamaModelIdentifier'); + +export class OllamaModel implements LanguageModel { + + protected readonly DEFAULT_REQUEST_SETTINGS: Partial> = { + keep_alive: '15m' + }; + + readonly providerId = 'ollama'; + readonly vendor: string = 'Ollama'; + + constructor(protected readonly model: string, protected host: () => string | undefined) { + } + + get id(): string { + return this.providerId + '/' + this.model; + } + + get name(): string { + return this.model; + } + + async request(request: LanguageModelRequest, cancellationToken?: CancellationToken): Promise { + const ollama = this.initializeOllama(); + + if (request.response_format?.type === 'json_schema') { + return this.handleStructuredOutputRequest(ollama, request); + } + const response = await ollama.chat({ + ...this.DEFAULT_REQUEST_SETTINGS, + model: this.model, + messages: request.messages.map(this.toOllamaMessage), + stream: true, + tools: request.tools?.map(this.toOllamaTool), + ...request.settings + }); + + cancellationToken?.onCancellationRequested(() => { + response.abort(); + }); + + async function* wrapAsyncIterator(inputIterable: AsyncIterable): AsyncIterable { + for await (const item of inputIterable) { + // TODO handle tool calls + yield { content: item.message.content }; + } + } + return { stream: wrapAsyncIterator(response) }; + } + + protected async handleStructuredOutputRequest(ollama: Ollama, request: LanguageModelRequest): Promise { + const result = await ollama.chat({ + ...this.DEFAULT_REQUEST_SETTINGS, + model: this.model, + messages: request.messages.map(this.toOllamaMessage), + format: 'json', + ...request.settings + }); + try { + return { + content: result.message.content, + parsed: JSON.parse(result.message.content) + }; + } catch (error) { + // TODO use ILogger + console.log('Failed to parse structured response from the language model.', error); + return { + content: result.message.content, + parsed: {} + }; + } + } + + protected initializeOllama(): Ollama { + const host = this.host(); + if (!host) { + throw new Error('Please provide OLLAMA_HOST in preferences or via environment variable'); + } + return new Ollama({ host: host }); + } + + protected toOllamaTool(tool: ToolRequest): Tool { + const transform = (props: Record | undefined) => { + if (!props) { + return undefined; + } + const result: Record = {}; + for (const key in props) { + if (Object.prototype.hasOwnProperty.call(props, key)) { + result[key] = { + type: props[key].type, + description: key + }; + } + } + return result; + }; + return { + type: 'function', + function: { + name: tool.name, + description: tool.description ?? 'Tool named ' + tool.name, + parameters: { + type: tool.parameters?.type ?? 'object', + required: Object.keys(tool.parameters?.properties ?? {}), + properties: transform(tool.parameters?.properties) ?? {} + }, + } + }; + } + + protected toOllamaMessage(message: LanguageModelRequestMessage): Message { + if (message.actor === 'ai') { + return { role: 'assistant', content: message.query || '' }; + } + if (message.actor === 'user') { + return { role: 'user', content: message.query || '' }; + } + if (message.actor === 'system') { + return { role: 'system', content: message.query || '' }; + } + return { role: 'system', content: '' }; + } +} diff --git a/packages/ai-ollama/src/node/ollama-language-models-manager-impl.ts b/packages/ai-ollama/src/node/ollama-language-models-manager-impl.ts new file mode 100644 index 0000000000000..1fbd1f520c3c8 --- /dev/null +++ b/packages/ai-ollama/src/node/ollama-language-models-manager-impl.ts @@ -0,0 +1,58 @@ +// ***************************************************************************** +// Copyright (C) 2024 TypeFox GmbH. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { LanguageModelRegistry } from '@theia/ai-core'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import { OllamaModel } from './ollama-language-model'; +import { OllamaLanguageModelsManager } from '../common'; + +@injectable() +export class OllamaLanguageModelsManagerImpl implements OllamaLanguageModelsManager { + + protected _host: string | undefined; + + @inject(LanguageModelRegistry) + protected readonly languageModelRegistry: LanguageModelRegistry; + + get host(): string | undefined { + return this._host ?? process.env.OLLAMA_HOST; + } + + // Triggered from frontend. In case you want to use the models on the backend + // without a frontend then call this yourself + async createLanguageModels(...modelIds: string[]): Promise { + for (const id of modelIds) { + // TODO check that the model exists in Ollama using `list`. Ask and trigger download if not. + if (!(await this.languageModelRegistry.getLanguageModel(`ollama/${id}`))) { + this.languageModelRegistry.addLanguageModels([new OllamaModel(id, () => this.host)]); + } else { + console.info(`Ollama: skip creating model ${id} because it already exists`); + } + } + } + + removeLanguageModels(...modelIds: string[]): void { + this.languageModelRegistry.removeLanguageModels(modelIds.map(id => `ollama/${id}`)); + } + + setHost(host: string | undefined): void { + if (host) { + this._host = host; + } else { + this._host = undefined; + } + } +} diff --git a/packages/ai-ollama/src/package.spec.ts b/packages/ai-ollama/src/package.spec.ts new file mode 100644 index 0000000000000..2b813c28eef54 --- /dev/null +++ b/packages/ai-ollama/src/package.spec.ts @@ -0,0 +1,27 @@ +// ***************************************************************************** +// Copyright (C) 2024 TypeFox GmbH and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** + +/* note: this bogus test file is required so that + we are able to run mocha unit tests on this + package, without having any actual unit tests in it. + This way a coverage report will be generated, + showing 0% coverage, instead of no report. + This file can be removed once we have real unit + tests in place. */ + +describe('ai-ollama package', () => { + it('support code coverage statistics', () => true); +}); diff --git a/packages/ai-ollama/tsconfig.json b/packages/ai-ollama/tsconfig.json new file mode 100644 index 0000000000000..61a997fc14fd1 --- /dev/null +++ b/packages/ai-ollama/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../configs/base.tsconfig", + "compilerOptions": { + "composite": true, + "rootDir": "src", + "outDir": "lib" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../ai-core" + }, + { + "path": "../core" + }, + { + "path": "../filesystem" + }, + { + "path": "../workspace" + } + ] +} diff --git a/tsconfig.json b/tsconfig.json index 3ae111513cd48..ee1bdb0025812 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -69,6 +69,9 @@ { "path": "packages/ai-history" }, + { + "path": "packages/ai-ollama" + }, { "path": "packages/ai-openai" }, diff --git a/yarn.lock b/yarn.lock index df0f13c9de69c..f009f74d98513 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2141,7 +2141,7 @@ "@types/node" "*" form-data "^4.0.0" -"@types/node@*", "@types/node@18", "@types/node@>=10.0.0", "@types/node@^10.14.22", "@types/node@^18.11.18": +"@types/node@*", "@types/node@18", "@types/node@>=10.0.0", "@types/node@^10.14.22", "@types/node@^18.11.18", "@types/node@^20.9.0": version "18.19.18" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.18.tgz#7526471b28828d1fef1f7e4960fb9477e6e4369c" integrity sha512-80CP7B8y4PzZF0GWx15/gVWRrB5y/bIjNI84NK3cmQJu0WZwvmj2WMA5LcofQFVfLqqCSp545+U2LsrVzX36Zg== @@ -3369,7 +3369,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.3.1: +base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -4970,13 +4970,13 @@ electron-window@^0.8.0: dependencies: is-electron-renderer "^2.0.0" -electron@^28.2.8: - version "28.2.10" - resolved "https://registry.yarnpkg.com/electron/-/electron-28.2.10.tgz#4e168568406a8b1e9b9a5859e988c905b9a57570" - integrity sha512-0rGBJNogcl2FIRxGRUv9zuMaBP78nSBJW+Bd1U7OGeg8IEkSIbHOhfn71XoGxgbOUSCEXjjyftq4mtAAVbUsZQ== +electron@^30.1.2: + version "30.5.0" + resolved "https://registry.yarnpkg.com/electron/-/electron-30.5.0.tgz#917a5dd1adbd6983ff8a96609035c3bff9821841" + integrity sha512-20c774PEsCFRzIgpZIaY6t/wTYxam0MfxQt5t+wG8aStHsl81FchUyLbDo4a59goqE7mHjLOdBDlxmZk88IgRw== dependencies: "@electron/get" "^2.0.0" - "@types/node" "^18.11.18" + "@types/node" "^20.9.0" extract-zip "^2.0.1" emoji-regex@^8.0.0: @@ -5678,6 +5678,11 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" +fflate@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + figures@3.2.0, figures@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -5851,6 +5856,11 @@ follow-redirects@^1.0.0, follow-redirects@^1.15.4: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + font-awesome@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133" @@ -7142,6 +7152,11 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== +isomorphic.js@^0.2.4: + version "0.2.5" + resolved "https://registry.yarnpkg.com/isomorphic.js/-/isomorphic.js-0.2.5.tgz#13eecf36f2dba53e85d355e11bf9d4208c6f7f88" + integrity sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw== + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" @@ -7610,6 +7625,13 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lib0@^0.2.52, lib0@^0.2.85, lib0@^0.2.86, lib0@^0.2.94: + version "0.2.97" + resolved "https://registry.yarnpkg.com/lib0/-/lib0-0.2.97.tgz#a68d7c88577ac1910cdbe5204bac070f07c8e0b4" + integrity sha512-Q4d1ekgvufi9FiHkkL46AhecfNjznSL9MRNoJRQ76gBHS9OqU2ArfQK0FvBpuxgWeJeNI0LVgAYMIpsGeX4gYg== + dependencies: + isomorphic.js "^0.2.4" + libnpmaccess@7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-7.0.2.tgz#7f056c8c933dd9c8ba771fa6493556b53c5aac52" @@ -8453,10 +8475,10 @@ mute-stream@~1.0.0: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== -nan@^2.14.0, nan@^2.17.0, nan@^2.18.0: - version "2.18.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" - integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== +nan@2.20.0, nan@^2.14.0, nan@^2.17.0, nan@^2.18.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3" + integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw== nano@^10.1.3: version "10.1.3" @@ -8643,6 +8665,16 @@ node-ssh@^12.0.1: shell-escape "^0.2.0" ssh2 "^1.5.0" +nodejs-file-downloader@4.13.0: + version "4.13.0" + resolved "https://registry.yarnpkg.com/nodejs-file-downloader/-/nodejs-file-downloader-4.13.0.tgz#da87c30081de5ff4e8b864062c98cdec03e66ad0" + integrity sha512-nI2fKnmJWWFZF6SgMPe1iBodKhfpztLKJTtCtNYGhm/9QXmWa/Pk9Sv00qHgzEvNLe1x7hjGDRor7gcm/ChaIQ== + dependencies: + follow-redirects "^1.15.6" + https-proxy-agent "^5.0.0" + mime-types "^2.1.27" + sanitize-filename "^1.6.3" + noop-logger@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" @@ -9025,6 +9057,13 @@ octicons@^7.1.0: dependencies: object-assign "^4.1.1" +ollama@^0.5.8: + version "0.5.8" + resolved "https://registry.yarnpkg.com/ollama/-/ollama-0.5.8.tgz#d52f20345b4b49e26734cf2e8749dd95899c2c99" + integrity sha512-frBGdfSV34i7JybLZUeyCYDx0CMyDiG4On8xOK+cNRWM04HImhoWgIMpF4p7vTkQumadbSxOteR7SZyKqNmOXg== + dependencies: + whatwg-fetch "^3.6.20" + on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -9046,6 +9085,26 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +open-collaboration-protocol@0.2.0, open-collaboration-protocol@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/open-collaboration-protocol/-/open-collaboration-protocol-0.2.0.tgz#f3f93f22bb5fbb46e3fd31e6bb87f52a9ce6526b" + integrity sha512-ZaLMTMyVoJJ0vPjoMXGhNZqiycbfyJPbNCkbI9uHTOYRsvZqreRAFhSd7p9RbxLJNS5xeQGNSfldrhhec94Bmg== + dependencies: + base64-js "^1.5.1" + fflate "^0.8.2" + msgpackr "^1.10.2" + semver "^7.6.2" + socket.io-client "^4.7.5" + +open-collaboration-yjs@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/open-collaboration-yjs/-/open-collaboration-yjs-0.2.0.tgz#7c7e30dba444b9f6947fe76ae02a7c3fdaec6172" + integrity sha512-HT2JU/HJObIaQMF/MHt5/5VdOnGn+bVTaTJnyYfyaa/vjqg4Z4Glas3Hc9Ua970ssP3cOIRUQoHQumM0giaxrw== + dependencies: + lib0 "^0.2.94" + open-collaboration-protocol "^0.2.0" + y-protocols "^1.0.6" + open@^7.4.2: version "7.4.2" resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" @@ -10417,6 +10476,13 @@ safe-regex-test@^1.0.3: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sanitize-filename@^1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378" + integrity sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg== + dependencies: + truncate-utf8-bytes "^1.0.0" + sax@>=0.6.0: version "1.3.0" resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0" @@ -10517,6 +10583,11 @@ semver@^7.0.0, semver@^7.1.1, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semve dependencies: lru-cache "^6.0.0" +semver@^7.6.2: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -10778,6 +10849,16 @@ socket.io-client@^4.5.3: engine.io-client "~6.5.2" socket.io-parser "~4.2.4" +socket.io-client@^4.7.5: + version "4.7.5" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.7.5.tgz#919be76916989758bdc20eec63f7ee0ae45c05b7" + integrity sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.5.2" + socket.io-parser "~4.2.4" + socket.io-parser@~4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" @@ -11510,6 +11591,13 @@ trim-repeated@^1.0.0: dependencies: escape-string-regexp "^1.0.2" +truncate-utf8-bytes@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" + integrity sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ== + dependencies: + utf8-byte-length "^1.0.1" + ts-api-utils@^1.0.1: version "1.3.0" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" @@ -11949,6 +12037,11 @@ user-home@^2.0.0: dependencies: os-homedir "^1.0.0" +utf8-byte-length@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz#f9f63910d15536ee2b2d5dd4665389715eac5c1e" + integrity sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA== + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -12183,6 +12276,11 @@ whatwg-encoding@^2.0.0: dependencies: iconv-lite "0.6.3" +whatwg-fetch@^3.6.20: + version "3.6.20" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70" + integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg== + whatwg-mimetype@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" @@ -12506,6 +12604,13 @@ xterm@^5.3.0: resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0.tgz#867daf9cc826f3d45b5377320aabd996cb0fce46" integrity sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg== +y-protocols@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/y-protocols/-/y-protocols-1.0.6.tgz#66dad8a95752623443e8e28c0e923682d2c0d495" + integrity sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q== + dependencies: + lib0 "^0.2.85" + y18n@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" @@ -12627,6 +12732,13 @@ yazl@^2.2.2: dependencies: buffer-crc32 "~0.2.3" +yjs@^13.6.7: + version "13.6.19" + resolved "https://registry.yarnpkg.com/yjs/-/yjs-13.6.19.tgz#66999f41254ab65be8c8e71bd767d124ad600909" + integrity sha512-GNKw4mEUn5yWU2QPHRx8jppxmCm9KzbBhB4qJLUJFiiYD0g/tDVgXQ7aPkyh01YO28kbs2J/BEbWBagjuWyejw== + dependencies: + lib0 "^0.2.86" + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"