@theia/ai-openai 1.46.0-next.241
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -0
- package/lib/browser/openai-frontend-application-contribution.d.ts +18 -0
- package/lib/browser/openai-frontend-application-contribution.d.ts.map +1 -0
- package/lib/browser/openai-frontend-application-contribution.js +143 -0
- package/lib/browser/openai-frontend-application-contribution.js.map +1 -0
- package/lib/browser/openai-frontend-module.d.ts +4 -0
- package/lib/browser/openai-frontend-module.d.ts.map +1 -0
- package/lib/browser/openai-frontend-module.js +32 -0
- package/lib/browser/openai-frontend-module.js.map +1 -0
- package/lib/browser/openai-preferences.d.ts +6 -0
- package/lib/browser/openai-preferences.d.ts.map +1 -0
- package/lib/browser/openai-preferences.js +84 -0
- package/lib/browser/openai-preferences.js.map +1 -0
- package/lib/common/index.d.ts +2 -0
- package/lib/common/index.d.ts.map +1 -0
- package/lib/common/index.js +20 -0
- package/lib/common/index.js.map +1 -0
- package/lib/common/openai-language-models-manager.d.ts +37 -0
- package/lib/common/openai-language-models-manager.d.ts.map +1 -0
- package/lib/common/openai-language-models-manager.js +21 -0
- package/lib/common/openai-language-models-manager.js.map +1 -0
- package/lib/node/openai-backend-module.d.ts +5 -0
- package/lib/node/openai-backend-module.d.ts.map +1 -0
- package/lib/node/openai-backend-module.js +29 -0
- package/lib/node/openai-backend-module.js.map +1 -0
- package/lib/node/openai-language-model.d.ts +36 -0
- package/lib/node/openai-language-model.d.ts.map +1 -0
- package/lib/node/openai-language-model.js +210 -0
- package/lib/node/openai-language-model.js.map +1 -0
- package/lib/node/openai-language-models-manager-impl.d.ts +11 -0
- package/lib/node/openai-language-models-manager-impl.d.ts.map +1 -0
- package/lib/node/openai-language-models-manager-impl.js +80 -0
- package/lib/node/openai-language-models-manager-impl.js.map +1 -0
- package/lib/package.spec.d.ts +1 -0
- package/lib/package.spec.d.ts.map +1 -0
- package/lib/package.spec.js +26 -0
- package/lib/package.spec.js.map +1 -0
- package/package.json +54 -0
- package/src/browser/openai-frontend-application-contribution.ts +162 -0
- package/src/browser/openai-frontend-module.ts +31 -0
- package/src/browser/openai-preferences.ts +84 -0
- package/src/common/index.ts +16 -0
- package/src/common/openai-language-models-manager.ts +49 -0
- package/src/node/openai-backend-module.ts +30 -0
- package/src/node/openai-language-model.ts +234 -0
- package/src/node/openai-language-models-manager-impl.ts +85 -0
- package/src/package.spec.ts +28 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { PreferenceSchema } from '@theia/core/lib/browser/preferences/preference-contribution';
|
|
18
|
+
import { AI_CORE_PREFERENCES_TITLE } from '@theia/ai-core/lib/browser/ai-core-preferences';
|
|
19
|
+
|
|
20
|
+
export const API_KEY_PREF = 'ai-features.openAiOfficial.openAiApiKey';
|
|
21
|
+
export const MODELS_PREF = 'ai-features.openAiOfficial.officialOpenAiModels';
|
|
22
|
+
export const CUSTOM_ENDPOINTS_PREF = 'ai-features.openAiCustom.customOpenAiModels';
|
|
23
|
+
|
|
24
|
+
export const OpenAiPreferencesSchema: PreferenceSchema = {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
[API_KEY_PREF]: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
markdownDescription: 'Enter an API Key of your official OpenAI Account. **Please note:** By using this preference the Open AI API key will be stored in clear text\
|
|
30
|
+
on the machine running Theia. Use the environment variable `OPENAI_API_KEY` to set the key securely.',
|
|
31
|
+
title: AI_CORE_PREFERENCES_TITLE,
|
|
32
|
+
},
|
|
33
|
+
[MODELS_PREF]: {
|
|
34
|
+
type: 'array',
|
|
35
|
+
description: 'Official OpenAI models to use',
|
|
36
|
+
title: AI_CORE_PREFERENCES_TITLE,
|
|
37
|
+
default: ['gpt-4o', 'gpt-4o-2024-08-06', 'gpt-4o-2024-05-13', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo', 'o1-preview'],
|
|
38
|
+
items: {
|
|
39
|
+
type: 'string'
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
[CUSTOM_ENDPOINTS_PREF]: {
|
|
43
|
+
type: 'array',
|
|
44
|
+
title: AI_CORE_PREFERENCES_TITLE,
|
|
45
|
+
markdownDescription: 'Integrate custom models compatible with the OpenAI API, for example via `vllm`. The required attributes are `model` and `url`.\
|
|
46
|
+
\n\
|
|
47
|
+
Optionally, you can\
|
|
48
|
+
\n\
|
|
49
|
+
- specify a unique `id` to identify the custom model in the UI. If none is given `model` will be used as `id`.\
|
|
50
|
+
\n\
|
|
51
|
+
- provide an `apiKey` to access the API served at the given url. Use `true` to indicate the use of the global OpenAI API key.\
|
|
52
|
+
\n\
|
|
53
|
+
- specify `enableStreaming: false` to indicate that streaming shall not be used.\
|
|
54
|
+
\n\
|
|
55
|
+
Refer to [our documentation](https://theia-ide.org/docs/user_ai/#openai-compatible-models-eg-via-vllm) for more information.',
|
|
56
|
+
default: [],
|
|
57
|
+
items: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
model: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
title: 'Model ID'
|
|
63
|
+
},
|
|
64
|
+
url: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
title: 'The Open AI API compatible endpoint where the model is hosted'
|
|
67
|
+
},
|
|
68
|
+
id: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
title: 'A unique identifier which is used in the UI to identify the custom model',
|
|
71
|
+
},
|
|
72
|
+
apiKey: {
|
|
73
|
+
type: ['string', 'boolean'],
|
|
74
|
+
title: 'Either the key to access the API served at the given url or `true` to use the global OpenAI API key',
|
|
75
|
+
},
|
|
76
|
+
enableStreaming: {
|
|
77
|
+
type: 'boolean',
|
|
78
|
+
title: 'Indicates whether the streaming API shall be used. `true` by default.',
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
export * from './openai-language-models-manager';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
export const OPENAI_LANGUAGE_MODELS_MANAGER_PATH = '/services/open-ai/language-model-manager';
|
|
17
|
+
export const OpenAiLanguageModelsManager = Symbol('OpenAiLanguageModelsManager');
|
|
18
|
+
export interface OpenAiModelDescription {
|
|
19
|
+
/**
|
|
20
|
+
* The identifier of the model which will be shown in the UI.
|
|
21
|
+
*/
|
|
22
|
+
id: string;
|
|
23
|
+
/**
|
|
24
|
+
* The model ID as used by the OpenAI API.
|
|
25
|
+
*/
|
|
26
|
+
model: string;
|
|
27
|
+
/**
|
|
28
|
+
* The OpenAI API compatible endpoint where the model is hosted. If not provided the default OpenAI endpoint will be used.
|
|
29
|
+
*/
|
|
30
|
+
url?: string;
|
|
31
|
+
/**
|
|
32
|
+
* The key for the model. If 'true' is provided the global OpenAI API key will be used.
|
|
33
|
+
*/
|
|
34
|
+
apiKey: string | true | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Indicate whether the streaming API shall be used.
|
|
37
|
+
*/
|
|
38
|
+
enableStreaming: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Default request settings for the OpenAI model.
|
|
41
|
+
*/
|
|
42
|
+
defaultRequestSettings?: { [key: string]: unknown };
|
|
43
|
+
}
|
|
44
|
+
export interface OpenAiLanguageModelsManager {
|
|
45
|
+
apiKey: string | undefined;
|
|
46
|
+
setApiKey(key: string | undefined): void;
|
|
47
|
+
createOrUpdateLanguageModels(...models: OpenAiModelDescription[]): Promise<void>;
|
|
48
|
+
removeLanguageModels(...modelIds: string[]): void
|
|
49
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { ContainerModule } from '@theia/core/shared/inversify';
|
|
18
|
+
import { OPENAI_LANGUAGE_MODELS_MANAGER_PATH, OpenAiLanguageModelsManager } from '../common/openai-language-models-manager';
|
|
19
|
+
import { ConnectionHandler, RpcConnectionHandler } from '@theia/core';
|
|
20
|
+
import { OpenAiLanguageModelsManagerImpl } from './openai-language-models-manager-impl';
|
|
21
|
+
|
|
22
|
+
export const OpenAiModelFactory = Symbol('OpenAiModelFactory');
|
|
23
|
+
|
|
24
|
+
export default new ContainerModule(bind => {
|
|
25
|
+
bind(OpenAiLanguageModelsManagerImpl).toSelf().inSingletonScope();
|
|
26
|
+
bind(OpenAiLanguageModelsManager).toService(OpenAiLanguageModelsManagerImpl);
|
|
27
|
+
bind(ConnectionHandler).toDynamicValue(ctx =>
|
|
28
|
+
new RpcConnectionHandler(OPENAI_LANGUAGE_MODELS_MANAGER_PATH, () => ctx.container.get(OpenAiLanguageModelsManager))
|
|
29
|
+
).inSingletonScope();
|
|
30
|
+
});
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
LanguageModel,
|
|
19
|
+
LanguageModelParsedResponse,
|
|
20
|
+
LanguageModelRequest,
|
|
21
|
+
LanguageModelRequestMessage,
|
|
22
|
+
LanguageModelResponse,
|
|
23
|
+
LanguageModelStreamResponsePart,
|
|
24
|
+
LanguageModelTextResponse
|
|
25
|
+
} from '@theia/ai-core';
|
|
26
|
+
import { CancellationToken } from '@theia/core';
|
|
27
|
+
import OpenAI from 'openai';
|
|
28
|
+
import { ChatCompletionStream } from 'openai/lib/ChatCompletionStream';
|
|
29
|
+
import { RunnableToolFunctionWithoutParse } from 'openai/lib/RunnableFunction';
|
|
30
|
+
import { ChatCompletionMessageParam } from 'openai/resources';
|
|
31
|
+
|
|
32
|
+
export const OpenAiModelIdentifier = Symbol('OpenAiModelIdentifier');
|
|
33
|
+
|
|
34
|
+
function toOpenAIMessage(message: LanguageModelRequestMessage): ChatCompletionMessageParam {
|
|
35
|
+
return {
|
|
36
|
+
role: toOpenAiRole(message),
|
|
37
|
+
content: message.query || ''
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function toOpenAiRole(message: LanguageModelRequestMessage): 'system' | 'user' | 'assistant' {
|
|
42
|
+
switch (message.actor) {
|
|
43
|
+
case 'system':
|
|
44
|
+
return 'system';
|
|
45
|
+
case 'ai':
|
|
46
|
+
return 'assistant';
|
|
47
|
+
default:
|
|
48
|
+
return 'user';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class OpenAiModel implements LanguageModel {
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param id the unique id for this language model. It will be used to identify the model in the UI.
|
|
56
|
+
* @param model the model id as it is used by the OpenAI API
|
|
57
|
+
* @param enableStreaming whether the streaming API shall be used
|
|
58
|
+
* @param apiKey a function that returns the API key to use for this model, called on each request
|
|
59
|
+
* @param url the OpenAI API compatible endpoint where the model is hosted. If not provided the default OpenAI endpoint will be used.
|
|
60
|
+
* @param defaultRequestSettings optional default settings for requests made using this model.
|
|
61
|
+
*/
|
|
62
|
+
constructor(
|
|
63
|
+
public readonly id: string,
|
|
64
|
+
public model: string,
|
|
65
|
+
public enableStreaming: boolean,
|
|
66
|
+
public apiKey: () => string | undefined,
|
|
67
|
+
public url: string | undefined,
|
|
68
|
+
public defaultRequestSettings?: { [key: string]: unknown }
|
|
69
|
+
) { }
|
|
70
|
+
|
|
71
|
+
protected getSettings(request: LanguageModelRequest): Record<string, unknown> {
|
|
72
|
+
const settings = request.settings ? request.settings : this.defaultRequestSettings;
|
|
73
|
+
if (!settings) {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
return settings;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async request(request: LanguageModelRequest, cancellationToken?: CancellationToken): Promise<LanguageModelResponse> {
|
|
80
|
+
const settings = this.getSettings(request);
|
|
81
|
+
const openai = this.initializeOpenAi();
|
|
82
|
+
|
|
83
|
+
if (this.isNonStreamingModel(this.model)) {
|
|
84
|
+
return this.handleNonStreamingRequest(openai, request);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (request.response_format?.type === 'json_schema' && this.supportsStructuredOutput()) {
|
|
88
|
+
return this.handleStructuredOutputRequest(openai, request);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let runner: ChatCompletionStream;
|
|
92
|
+
const tools = this.createTools(request);
|
|
93
|
+
if (tools) {
|
|
94
|
+
runner = openai.beta.chat.completions.runTools({
|
|
95
|
+
model: this.model,
|
|
96
|
+
messages: request.messages.map(toOpenAIMessage),
|
|
97
|
+
stream: true,
|
|
98
|
+
tools: tools,
|
|
99
|
+
tool_choice: 'auto',
|
|
100
|
+
...settings
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
runner = openai.beta.chat.completions.stream({
|
|
104
|
+
model: this.model,
|
|
105
|
+
messages: request.messages.map(toOpenAIMessage),
|
|
106
|
+
stream: true,
|
|
107
|
+
...settings
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
cancellationToken?.onCancellationRequested(() => {
|
|
111
|
+
runner.abort();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
let runnerEnd = false;
|
|
115
|
+
|
|
116
|
+
let resolve: (part: LanguageModelStreamResponsePart) => void;
|
|
117
|
+
runner.on('error', error => {
|
|
118
|
+
console.error('Error in OpenAI chat completion stream:', error);
|
|
119
|
+
runnerEnd = true;
|
|
120
|
+
resolve({ content: error.message });
|
|
121
|
+
});
|
|
122
|
+
// we need to also listen for the emitted errors, as otherwise any error actually thrown by the API will not be caught
|
|
123
|
+
runner.emitted('error').then(error => {
|
|
124
|
+
console.error('Error in OpenAI chat completion stream:', error);
|
|
125
|
+
runnerEnd = true;
|
|
126
|
+
resolve({ content: error.message });
|
|
127
|
+
});
|
|
128
|
+
runner.emitted('abort').then(() => {
|
|
129
|
+
// do nothing, as the abort event is only emitted when the runner is aborted by us
|
|
130
|
+
});
|
|
131
|
+
runner.on('message', message => {
|
|
132
|
+
if (message.role === 'tool') {
|
|
133
|
+
resolve({ tool_calls: [{ id: message.tool_call_id, finished: true, result: this.getCompletionContent(message) }] });
|
|
134
|
+
}
|
|
135
|
+
console.debug('Received Open AI message', JSON.stringify(message));
|
|
136
|
+
});
|
|
137
|
+
runner.once('end', () => {
|
|
138
|
+
runnerEnd = true;
|
|
139
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
140
|
+
resolve(runner.finalChatCompletion as any);
|
|
141
|
+
});
|
|
142
|
+
const asyncIterator = {
|
|
143
|
+
async *[Symbol.asyncIterator](): AsyncIterator<LanguageModelStreamResponsePart> {
|
|
144
|
+
runner.on('chunk', chunk => {
|
|
145
|
+
if (chunk.choices[0]?.delta) {
|
|
146
|
+
resolve({ ...chunk.choices[0]?.delta });
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
while (!runnerEnd) {
|
|
150
|
+
const promise = new Promise<LanguageModelStreamResponsePart>((res, rej) => {
|
|
151
|
+
resolve = res;
|
|
152
|
+
});
|
|
153
|
+
yield promise;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
return { stream: asyncIterator };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
protected async handleNonStreamingRequest(openai: OpenAI, request: LanguageModelRequest): Promise<LanguageModelTextResponse> {
|
|
161
|
+
const settings = this.getSettings(request);
|
|
162
|
+
const response = await openai.chat.completions.create({
|
|
163
|
+
model: this.model,
|
|
164
|
+
messages: request.messages.map(toOpenAIMessage),
|
|
165
|
+
...settings
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const message = response.choices[0].message;
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
text: message.content ?? ''
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
protected isNonStreamingModel(_model: string): boolean {
|
|
176
|
+
return !this.enableStreaming;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
protected supportsStructuredOutput(): boolean {
|
|
180
|
+
// see https://platform.openai.com/docs/models/gpt-4o
|
|
181
|
+
return [
|
|
182
|
+
'gpt-4o',
|
|
183
|
+
'gpt-4o-2024-08-06',
|
|
184
|
+
'gpt-4o-mini'
|
|
185
|
+
].includes(this.model);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
protected async handleStructuredOutputRequest(openai: OpenAI, request: LanguageModelRequest): Promise<LanguageModelParsedResponse> {
|
|
189
|
+
const settings = this.getSettings(request);
|
|
190
|
+
// TODO implement tool support for structured output (parse() seems to require different tool format)
|
|
191
|
+
const result = await openai.beta.chat.completions.parse({
|
|
192
|
+
model: this.model,
|
|
193
|
+
messages: request.messages.map(toOpenAIMessage),
|
|
194
|
+
response_format: request.response_format,
|
|
195
|
+
...settings
|
|
196
|
+
});
|
|
197
|
+
const message = result.choices[0].message;
|
|
198
|
+
if (message.refusal || message.parsed === undefined) {
|
|
199
|
+
console.error('Error in OpenAI chat completion stream:', JSON.stringify(message));
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
content: message.content ?? '',
|
|
203
|
+
parsed: message.parsed
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private getCompletionContent(message: OpenAI.Chat.Completions.ChatCompletionToolMessageParam): string {
|
|
208
|
+
if (Array.isArray(message.content)) {
|
|
209
|
+
return message.content.join('');
|
|
210
|
+
}
|
|
211
|
+
return message.content;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
protected createTools(request: LanguageModelRequest): RunnableToolFunctionWithoutParse[] | undefined {
|
|
215
|
+
return request.tools?.map(tool => ({
|
|
216
|
+
type: 'function',
|
|
217
|
+
function: {
|
|
218
|
+
name: tool.name,
|
|
219
|
+
description: tool.description,
|
|
220
|
+
parameters: tool.parameters,
|
|
221
|
+
function: (args_string: string) => tool.handler(args_string)
|
|
222
|
+
}
|
|
223
|
+
} as RunnableToolFunctionWithoutParse));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
protected initializeOpenAi(): OpenAI {
|
|
227
|
+
const apiKey = this.apiKey();
|
|
228
|
+
if (!apiKey && !(this.url)) {
|
|
229
|
+
throw new Error('Please provide OPENAI_API_KEY in preferences or via environment variable');
|
|
230
|
+
}
|
|
231
|
+
// We need to hand over "some" key, even if a custom url is not key protected as otherwise the OpenAI client will throw an error
|
|
232
|
+
return new OpenAI({ apiKey: apiKey ?? 'no-key', baseURL: this.url });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { LanguageModelRegistry } from '@theia/ai-core';
|
|
18
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
19
|
+
import { OpenAiModel } from './openai-language-model';
|
|
20
|
+
import { OpenAiLanguageModelsManager, OpenAiModelDescription } from '../common';
|
|
21
|
+
|
|
22
|
+
@injectable()
|
|
23
|
+
export class OpenAiLanguageModelsManagerImpl implements OpenAiLanguageModelsManager {
|
|
24
|
+
|
|
25
|
+
protected _apiKey: string | undefined;
|
|
26
|
+
|
|
27
|
+
@inject(LanguageModelRegistry)
|
|
28
|
+
protected readonly languageModelRegistry: LanguageModelRegistry;
|
|
29
|
+
|
|
30
|
+
get apiKey(): string | undefined {
|
|
31
|
+
return this._apiKey ?? process.env.OPENAI_API_KEY;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Triggered from frontend. In case you want to use the models on the backend
|
|
35
|
+
// without a frontend then call this yourself
|
|
36
|
+
async createOrUpdateLanguageModels(...modelDescriptions: OpenAiModelDescription[]): Promise<void> {
|
|
37
|
+
for (const modelDescription of modelDescriptions) {
|
|
38
|
+
const model = await this.languageModelRegistry.getLanguageModel(modelDescription.id);
|
|
39
|
+
const apiKeyProvider = () => {
|
|
40
|
+
if (modelDescription.apiKey === true) {
|
|
41
|
+
return this.apiKey;
|
|
42
|
+
}
|
|
43
|
+
if (modelDescription.apiKey) {
|
|
44
|
+
return modelDescription.apiKey;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (model) {
|
|
50
|
+
if (!(model instanceof OpenAiModel)) {
|
|
51
|
+
console.warn(`OpenAI: model ${modelDescription.id} is not an OpenAI model`);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
model.model = modelDescription.model;
|
|
55
|
+
model.enableStreaming = modelDescription.enableStreaming;
|
|
56
|
+
model.url = modelDescription.url;
|
|
57
|
+
model.apiKey = apiKeyProvider;
|
|
58
|
+
model.defaultRequestSettings = modelDescription.defaultRequestSettings;
|
|
59
|
+
} else {
|
|
60
|
+
this.languageModelRegistry.addLanguageModels([
|
|
61
|
+
new OpenAiModel(
|
|
62
|
+
modelDescription.id,
|
|
63
|
+
modelDescription.model,
|
|
64
|
+
modelDescription.enableStreaming,
|
|
65
|
+
apiKeyProvider,
|
|
66
|
+
modelDescription.url,
|
|
67
|
+
modelDescription.defaultRequestSettings
|
|
68
|
+
)
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
removeLanguageModels(...modelIds: string[]): void {
|
|
75
|
+
this.languageModelRegistry.removeLanguageModels(modelIds);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
setApiKey(apiKey: string | undefined): void {
|
|
79
|
+
if (apiKey) {
|
|
80
|
+
this._apiKey = apiKey;
|
|
81
|
+
} else {
|
|
82
|
+
this._apiKey = undefined;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
/* note: this bogus test file is required so that
|
|
18
|
+
we are able to run mocha unit tests on this
|
|
19
|
+
package, without having any actual unit tests in it.
|
|
20
|
+
This way a coverage report will be generated,
|
|
21
|
+
showing 0% coverage, instead of no report.
|
|
22
|
+
This file can be removed once we have real unit
|
|
23
|
+
tests in place. */
|
|
24
|
+
|
|
25
|
+
describe('ai-openai package', () => {
|
|
26
|
+
|
|
27
|
+
it('support code coverage statistics', () => true);
|
|
28
|
+
});
|