@jupyterlite/ai 0.3.0 → 0.4.0
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/lib/chat-handler.d.ts +3 -3
- package/lib/chat-handler.js +13 -10
- package/lib/completion-provider.d.ts +5 -18
- package/lib/completion-provider.js +8 -34
- package/lib/index.d.ts +2 -2
- package/lib/index.js +44 -42
- package/lib/llm-models/index.d.ts +3 -2
- package/lib/llm-models/index.js +42 -2
- package/lib/provider.d.ts +44 -16
- package/lib/provider.js +97 -41
- package/lib/settings/instructions.d.ts +2 -0
- package/lib/settings/instructions.js +44 -0
- package/lib/settings/panel.d.ts +70 -0
- package/lib/settings/panel.js +190 -0
- package/lib/settings/schemas/base.json +7 -0
- package/lib/settings/schemas/index.d.ts +3 -0
- package/lib/settings/schemas/index.js +11 -0
- package/lib/tokens.d.ts +103 -0
- package/lib/tokens.js +5 -0
- package/package.json +5 -1
- package/schema/provider-registry.json +17 -0
- package/src/chat-handler.ts +16 -13
- package/src/completion-provider.ts +13 -37
- package/src/index.ts +59 -44
- package/src/llm-models/index.ts +49 -2
- package/src/provider.ts +100 -43
- package/src/settings/instructions.ts +48 -0
- package/src/settings/panel.tsx +257 -0
- package/src/settings/schemas/index.ts +15 -0
- package/src/tokens.ts +112 -0
- package/style/base.css +4 -0
- package/lib/llm-models/utils.d.ts +0 -16
- package/lib/llm-models/utils.js +0 -86
- package/lib/token.d.ts +0 -13
- package/lib/token.js +0 -2
- package/schema/ai-provider.json +0 -17
- package/src/llm-models/utils.ts +0 -90
- package/src/token.ts +0 -19
- /package/lib/{_provider-settings/anthropic.json → settings/schemas/_generated/Anthropic.json} +0 -0
- /package/lib/{_provider-settings/chromeAI.json → settings/schemas/_generated/ChromeAI.json} +0 -0
- /package/lib/{_provider-settings/mistralAI.json → settings/schemas/_generated/MistralAI.json} +0 -0
- /package/lib/{_provider-settings/openAI.json → settings/schemas/_generated/OpenAI.json} +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "AI provider",
|
|
3
|
+
"description": "Provider registry settings",
|
|
4
|
+
"jupyter.lab.setting-icon": "@jupyterlite/ai:jupyternaut-lite",
|
|
5
|
+
"jupyter.lab.setting-icon-label": "JupyterLite AI Chat",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"AIprovider": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"title": "AI provider",
|
|
11
|
+
"description": "The AI provider configuration",
|
|
12
|
+
"default": {},
|
|
13
|
+
"additionalProperties": true
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"additionalProperties": false
|
|
17
|
+
}
|
package/src/chat-handler.ts
CHANGED
|
@@ -17,9 +17,8 @@ import {
|
|
|
17
17
|
SystemMessage
|
|
18
18
|
} from '@langchain/core/messages';
|
|
19
19
|
import { UUID } from '@lumino/coreutils';
|
|
20
|
-
import { getErrorMessage } from './llm-models';
|
|
21
20
|
import { chatSystemPrompt } from './provider';
|
|
22
|
-
import {
|
|
21
|
+
import { IAIProviderRegistry } from './tokens';
|
|
23
22
|
import { jupyternautLiteIcon } from './icons';
|
|
24
23
|
|
|
25
24
|
/**
|
|
@@ -37,17 +36,21 @@ export type ConnectionMessage = {
|
|
|
37
36
|
export class ChatHandler extends ChatModel {
|
|
38
37
|
constructor(options: ChatHandler.IOptions) {
|
|
39
38
|
super(options);
|
|
40
|
-
this.
|
|
41
|
-
this._prompt = chatSystemPrompt({
|
|
39
|
+
this._providerRegistry = options.providerRegistry;
|
|
40
|
+
this._prompt = chatSystemPrompt({
|
|
41
|
+
provider_name: this._providerRegistry.currentName
|
|
42
|
+
});
|
|
42
43
|
|
|
43
|
-
this.
|
|
44
|
-
this._errorMessage = this.
|
|
45
|
-
this._prompt = chatSystemPrompt({
|
|
44
|
+
this._providerRegistry.providerChanged.connect(() => {
|
|
45
|
+
this._errorMessage = this._providerRegistry.chatError;
|
|
46
|
+
this._prompt = chatSystemPrompt({
|
|
47
|
+
provider_name: this._providerRegistry.currentName
|
|
48
|
+
});
|
|
46
49
|
});
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
get provider(): BaseChatModel | null {
|
|
50
|
-
return this.
|
|
53
|
+
return this._providerRegistry.currentChatModel;
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
/**
|
|
@@ -95,7 +98,7 @@ export class ChatHandler extends ChatModel {
|
|
|
95
98
|
};
|
|
96
99
|
this.messageAdded(msg);
|
|
97
100
|
|
|
98
|
-
if (this.
|
|
101
|
+
if (this._providerRegistry.currentChatModel === null) {
|
|
99
102
|
const errorMsg: IChatMessage = {
|
|
100
103
|
id: UUID.uuid4(),
|
|
101
104
|
body: `**${this._errorMessage ? this._errorMessage : this._defaultErrorMessage}**`,
|
|
@@ -134,7 +137,7 @@ export class ChatHandler extends ChatModel {
|
|
|
134
137
|
let content = '';
|
|
135
138
|
|
|
136
139
|
try {
|
|
137
|
-
for await (const chunk of await this.
|
|
140
|
+
for await (const chunk of await this._providerRegistry.currentChatModel.stream(
|
|
138
141
|
messages
|
|
139
142
|
)) {
|
|
140
143
|
content += chunk.content ?? chunk;
|
|
@@ -144,7 +147,7 @@ export class ChatHandler extends ChatModel {
|
|
|
144
147
|
this._history.messages.push(botMsg);
|
|
145
148
|
return true;
|
|
146
149
|
} catch (reason) {
|
|
147
|
-
const error =
|
|
150
|
+
const error = this._providerRegistry.formatErrorMessage(reason);
|
|
148
151
|
const errorMsg: IChatMessage = {
|
|
149
152
|
id: UUID.uuid4(),
|
|
150
153
|
body: `**${error}**`,
|
|
@@ -171,7 +174,7 @@ export class ChatHandler extends ChatModel {
|
|
|
171
174
|
super.messageAdded(message);
|
|
172
175
|
}
|
|
173
176
|
|
|
174
|
-
private
|
|
177
|
+
private _providerRegistry: IAIProviderRegistry;
|
|
175
178
|
private _personaName = 'AI';
|
|
176
179
|
private _prompt: string;
|
|
177
180
|
private _errorMessage: string = '';
|
|
@@ -181,6 +184,6 @@ export class ChatHandler extends ChatModel {
|
|
|
181
184
|
|
|
182
185
|
export namespace ChatHandler {
|
|
183
186
|
export interface IOptions extends ChatModel.IOptions {
|
|
184
|
-
|
|
187
|
+
providerRegistry: IAIProviderRegistry;
|
|
185
188
|
}
|
|
186
189
|
}
|
|
@@ -3,10 +3,9 @@ import {
|
|
|
3
3
|
IInlineCompletionContext,
|
|
4
4
|
IInlineCompletionProvider
|
|
5
5
|
} from '@jupyterlab/completer';
|
|
6
|
-
import { BaseLanguageModel } from '@langchain/core/language_models/base';
|
|
7
|
-
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
8
6
|
|
|
9
|
-
import {
|
|
7
|
+
import { IBaseCompleter } from './llm-models';
|
|
8
|
+
import { IAIProviderRegistry } from './tokens';
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* The generic completion provider to register to the completion provider manager.
|
|
@@ -15,67 +14,44 @@ export class CompletionProvider implements IInlineCompletionProvider {
|
|
|
15
14
|
readonly identifier = '@jupyterlite/ai';
|
|
16
15
|
|
|
17
16
|
constructor(options: CompletionProvider.IOptions) {
|
|
18
|
-
|
|
17
|
+
this._providerRegistry = options.providerRegistry;
|
|
19
18
|
this._requestCompletion = options.requestCompletion;
|
|
20
|
-
this.setCompleter(name, settings);
|
|
21
|
-
}
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* @param name - the name of the completer.
|
|
27
|
-
* @param settings - The settings associated to the completer.
|
|
28
|
-
*/
|
|
29
|
-
setCompleter(name: string, settings: ReadonlyPartialJSONObject) {
|
|
30
|
-
try {
|
|
31
|
-
this._completer = getCompleter(name, settings);
|
|
32
|
-
if (this._completer) {
|
|
33
|
-
this._completer.requestCompletion = this._requestCompletion;
|
|
20
|
+
this._providerRegistry.providerChanged.connect(() => {
|
|
21
|
+
if (this.completer) {
|
|
22
|
+
this.completer.requestCompletion = this._requestCompletion;
|
|
34
23
|
}
|
|
35
|
-
|
|
36
|
-
} catch (e: any) {
|
|
37
|
-
this._completer = null;
|
|
38
|
-
this._name = 'None';
|
|
39
|
-
throw e;
|
|
40
|
-
}
|
|
24
|
+
});
|
|
41
25
|
}
|
|
42
26
|
|
|
43
27
|
/**
|
|
44
28
|
* Get the current completer name.
|
|
45
29
|
*/
|
|
46
30
|
get name(): string {
|
|
47
|
-
return this.
|
|
31
|
+
return this._providerRegistry.currentName;
|
|
48
32
|
}
|
|
49
33
|
|
|
50
34
|
/**
|
|
51
35
|
* Get the current completer.
|
|
52
36
|
*/
|
|
53
37
|
get completer(): IBaseCompleter | null {
|
|
54
|
-
return this.
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Get the LLM completer.
|
|
59
|
-
*/
|
|
60
|
-
get llmCompleter(): BaseLanguageModel | null {
|
|
61
|
-
return this._completer?.provider || null;
|
|
38
|
+
return this._providerRegistry.currentCompleter;
|
|
62
39
|
}
|
|
63
40
|
|
|
64
41
|
async fetch(
|
|
65
42
|
request: CompletionHandler.IRequest,
|
|
66
43
|
context: IInlineCompletionContext
|
|
67
44
|
) {
|
|
68
|
-
return this.
|
|
45
|
+
return this.completer?.fetch(request, context);
|
|
69
46
|
}
|
|
70
47
|
|
|
71
|
-
private
|
|
48
|
+
private _providerRegistry: IAIProviderRegistry;
|
|
72
49
|
private _requestCompletion: () => void;
|
|
73
|
-
private _completer: IBaseCompleter | null = null;
|
|
74
50
|
}
|
|
75
51
|
|
|
76
52
|
export namespace CompletionProvider {
|
|
77
|
-
export interface IOptions
|
|
78
|
-
|
|
53
|
+
export interface IOptions {
|
|
54
|
+
providerRegistry: IAIProviderRegistry;
|
|
79
55
|
requestCompletion: () => void;
|
|
80
56
|
}
|
|
81
57
|
}
|
package/src/index.ts
CHANGED
|
@@ -16,12 +16,16 @@ import { ICompletionProviderManager } from '@jupyterlab/completer';
|
|
|
16
16
|
import { INotebookTracker } from '@jupyterlab/notebook';
|
|
17
17
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
18
18
|
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
19
|
+
import { IFormRendererRegistry } from '@jupyterlab/ui-components';
|
|
20
|
+
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
19
21
|
|
|
20
22
|
import { ChatHandler } from './chat-handler';
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
+
import { CompletionProvider } from './completion-provider';
|
|
24
|
+
import { AIProviders } from './llm-models';
|
|
25
|
+
import { AIProviderRegistry } from './provider';
|
|
26
|
+
import { aiSettingsRenderer } from './settings/panel';
|
|
23
27
|
import { renderSlashCommandOption } from './slash-commands';
|
|
24
|
-
import {
|
|
28
|
+
import { IAIProviderRegistry } from './tokens';
|
|
25
29
|
|
|
26
30
|
const autocompletionRegistryPlugin: JupyterFrontEndPlugin<IAutocompletionRegistry> =
|
|
27
31
|
{
|
|
@@ -54,11 +58,11 @@ const chatPlugin: JupyterFrontEndPlugin<void> = {
|
|
|
54
58
|
id: '@jupyterlite/ai:chat',
|
|
55
59
|
description: 'LLM chat extension',
|
|
56
60
|
autoStart: true,
|
|
57
|
-
requires: [
|
|
61
|
+
requires: [IAIProviderRegistry, IRenderMimeRegistry, IAutocompletionRegistry],
|
|
58
62
|
optional: [INotebookTracker, ISettingRegistry, IThemeManager],
|
|
59
63
|
activate: async (
|
|
60
64
|
app: JupyterFrontEnd,
|
|
61
|
-
|
|
65
|
+
providerRegistry: IAIProviderRegistry,
|
|
62
66
|
rmRegistry: IRenderMimeRegistry,
|
|
63
67
|
autocompletionRegistry: IAutocompletionRegistry,
|
|
64
68
|
notebookTracker: INotebookTracker | null,
|
|
@@ -74,8 +78,8 @@ const chatPlugin: JupyterFrontEndPlugin<void> = {
|
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
const chatHandler = new ChatHandler({
|
|
77
|
-
|
|
78
|
-
activeCellManager
|
|
81
|
+
providerRegistry,
|
|
82
|
+
activeCellManager
|
|
79
83
|
});
|
|
80
84
|
|
|
81
85
|
let sendWithShiftEnter = false;
|
|
@@ -129,50 +133,53 @@ const chatPlugin: JupyterFrontEndPlugin<void> = {
|
|
|
129
133
|
}
|
|
130
134
|
};
|
|
131
135
|
|
|
132
|
-
const
|
|
133
|
-
id: '@jupyterlite/ai:
|
|
136
|
+
const completerPlugin: JupyterFrontEndPlugin<void> = {
|
|
137
|
+
id: '@jupyterlite/ai:completer',
|
|
134
138
|
autoStart: true,
|
|
135
|
-
requires: [
|
|
136
|
-
provides: IAIProvider,
|
|
139
|
+
requires: [IAIProviderRegistry, ICompletionProviderManager],
|
|
137
140
|
activate: (
|
|
138
141
|
app: JupyterFrontEnd,
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
):
|
|
142
|
-
const
|
|
143
|
-
|
|
142
|
+
providerRegistry: IAIProviderRegistry,
|
|
143
|
+
manager: ICompletionProviderManager
|
|
144
|
+
): void => {
|
|
145
|
+
const completer = new CompletionProvider({
|
|
146
|
+
providerRegistry,
|
|
144
147
|
requestCompletion: () => app.commands.execute('inline-completer:invoke')
|
|
145
148
|
});
|
|
149
|
+
manager.registerInlineProvider(completer);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
146
152
|
|
|
147
|
-
|
|
153
|
+
const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
|
|
154
|
+
id: '@jupyterlite/ai:provider-registry',
|
|
155
|
+
autoStart: true,
|
|
156
|
+
requires: [IFormRendererRegistry, ISettingRegistry],
|
|
157
|
+
optional: [IRenderMimeRegistry],
|
|
158
|
+
provides: IAIProviderRegistry,
|
|
159
|
+
activate: (
|
|
160
|
+
app: JupyterFrontEnd,
|
|
161
|
+
editorRegistry: IFormRendererRegistry,
|
|
162
|
+
settingRegistry: ISettingRegistry,
|
|
163
|
+
rmRegistry?: IRenderMimeRegistry
|
|
164
|
+
): IAIProviderRegistry => {
|
|
165
|
+
const providerRegistry = new AIProviderRegistry();
|
|
166
|
+
|
|
167
|
+
editorRegistry.addRenderer(
|
|
168
|
+
'@jupyterlite/ai:provider-registry.AIprovider',
|
|
169
|
+
aiSettingsRenderer({ providerRegistry, rmRegistry })
|
|
170
|
+
);
|
|
148
171
|
settingRegistry
|
|
149
|
-
.load(
|
|
172
|
+
.load(providerRegistryPlugin.id)
|
|
150
173
|
.then(settings => {
|
|
151
174
|
const updateProvider = () => {
|
|
152
|
-
const provider = settings.get('provider').composite as string;
|
|
153
|
-
if (provider !== currentProvider) {
|
|
154
|
-
// Update the settings panel.
|
|
155
|
-
currentProvider = provider;
|
|
156
|
-
const settingsProperties = settings.schema.properties;
|
|
157
|
-
if (settingsProperties) {
|
|
158
|
-
const schemaKeys = Object.keys(settingsProperties);
|
|
159
|
-
schemaKeys.forEach(key => {
|
|
160
|
-
if (key !== 'provider') {
|
|
161
|
-
delete settings.schema.properties?.[key];
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
const properties = getSettings(provider);
|
|
165
|
-
if (properties === null) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
Object.entries(properties).forEach(([name, value], index) => {
|
|
169
|
-
settingsProperties[name] = value as ISettingRegistry.IProperty;
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
175
|
// Update the settings to the AI providers.
|
|
175
|
-
|
|
176
|
+
const providerSettings = (settings.get('AIprovider').composite ?? {
|
|
177
|
+
provider: 'None'
|
|
178
|
+
}) as ReadonlyPartialJSONObject;
|
|
179
|
+
providerRegistry.setProvider(
|
|
180
|
+
providerSettings.provider as string,
|
|
181
|
+
providerSettings
|
|
182
|
+
);
|
|
176
183
|
};
|
|
177
184
|
|
|
178
185
|
settings.changed.connect(() => updateProvider());
|
|
@@ -180,13 +187,21 @@ const aiProviderPlugin: JupyterFrontEndPlugin<IAIProvider> = {
|
|
|
180
187
|
})
|
|
181
188
|
.catch(reason => {
|
|
182
189
|
console.error(
|
|
183
|
-
`Failed to load settings for ${
|
|
190
|
+
`Failed to load settings for ${providerRegistryPlugin.id}`,
|
|
184
191
|
reason
|
|
185
192
|
);
|
|
186
193
|
});
|
|
187
194
|
|
|
188
|
-
|
|
195
|
+
// Initialize the registry with the default providers
|
|
196
|
+
AIProviders.forEach(provider => providerRegistry.add(provider));
|
|
197
|
+
|
|
198
|
+
return providerRegistry;
|
|
189
199
|
}
|
|
190
200
|
};
|
|
191
201
|
|
|
192
|
-
export default [
|
|
202
|
+
export default [
|
|
203
|
+
providerRegistryPlugin,
|
|
204
|
+
autocompletionRegistryPlugin,
|
|
205
|
+
chatPlugin,
|
|
206
|
+
completerPlugin
|
|
207
|
+
];
|
package/src/llm-models/index.ts
CHANGED
|
@@ -1,3 +1,50 @@
|
|
|
1
|
+
import { ChatAnthropic } from '@langchain/anthropic';
|
|
2
|
+
import { ChromeAI } from '@langchain/community/experimental/llms/chrome_ai';
|
|
3
|
+
import { ChatMistralAI } from '@langchain/mistralai';
|
|
4
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
5
|
+
|
|
6
|
+
import { AnthropicCompleter } from './anthropic-completer';
|
|
7
|
+
import { CodestralCompleter } from './codestral-completer';
|
|
8
|
+
import { ChromeCompleter } from './chrome-completer';
|
|
9
|
+
import { OpenAICompleter } from './openai-completer';
|
|
10
|
+
|
|
11
|
+
import { instructions } from '../settings/instructions';
|
|
12
|
+
import { ProviderSettings } from '../settings/schemas';
|
|
13
|
+
|
|
14
|
+
import { IAIProvider } from '../tokens';
|
|
15
|
+
|
|
1
16
|
export * from './base-completer';
|
|
2
|
-
|
|
3
|
-
|
|
17
|
+
|
|
18
|
+
const AIProviders: IAIProvider[] = [
|
|
19
|
+
{
|
|
20
|
+
name: 'Anthropic',
|
|
21
|
+
chatModel: ChatAnthropic,
|
|
22
|
+
completer: AnthropicCompleter,
|
|
23
|
+
settingsSchema: ProviderSettings.Anthropic,
|
|
24
|
+
errorMessage: (error: any) => error.error.error.message
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'ChromeAI',
|
|
28
|
+
// TODO: fix
|
|
29
|
+
// @ts-expect-error: missing properties
|
|
30
|
+
chatModel: ChromeAI,
|
|
31
|
+
completer: ChromeCompleter,
|
|
32
|
+
instructions: instructions.ChromeAI,
|
|
33
|
+
settingsSchema: ProviderSettings.ChromeAI
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'MistralAI',
|
|
37
|
+
chatModel: ChatMistralAI,
|
|
38
|
+
completer: CodestralCompleter,
|
|
39
|
+
instructions: instructions.MistralAI,
|
|
40
|
+
settingsSchema: ProviderSettings.MistralAI
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'OpenAI',
|
|
44
|
+
chatModel: ChatOpenAI,
|
|
45
|
+
completer: OpenAICompleter,
|
|
46
|
+
settingsSchema: ProviderSettings.OpenAI
|
|
47
|
+
}
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
export { AIProviders };
|
package/src/provider.ts
CHANGED
|
@@ -4,11 +4,13 @@ import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
|
4
4
|
import { ISignal, Signal } from '@lumino/signaling';
|
|
5
5
|
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { IBaseCompleter } from './llm-models';
|
|
8
|
+
import { IAIProvider, IAIProviderRegistry } from './tokens';
|
|
9
|
+
import { JSONSchema7 } from 'json-schema';
|
|
10
10
|
|
|
11
|
-
export const chatSystemPrompt = (
|
|
11
|
+
export const chatSystemPrompt = (
|
|
12
|
+
options: AIProviderRegistry.IPromptOptions
|
|
13
|
+
) => `
|
|
12
14
|
You are Jupyternaut, a conversational assistant living in JupyterLab to help users.
|
|
13
15
|
You are not a language model, but rather an application built on a foundation model from ${options.provider_name}.
|
|
14
16
|
You are talkative and you provide lots of specific details from the foundation model's context.
|
|
@@ -36,40 +38,79 @@ would write.
|
|
|
36
38
|
Do not include the prompt in the output, only the string that should be appended to the current input.
|
|
37
39
|
`;
|
|
38
40
|
|
|
39
|
-
export class
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
export class AIProviderRegistry implements IAIProviderRegistry {
|
|
42
|
+
/**
|
|
43
|
+
* Get the list of provider names.
|
|
44
|
+
*/
|
|
45
|
+
get providers(): string[] {
|
|
46
|
+
return Array.from(this._providers.keys());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Add a new provider.
|
|
51
|
+
*/
|
|
52
|
+
add(provider: IAIProvider): void {
|
|
53
|
+
if (this._providers.has(provider.name)) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`A AI provider named '${provider.name}' is already registered`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
this._providers.set(provider.name, provider);
|
|
49
59
|
}
|
|
50
60
|
|
|
51
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Get the current provider name.
|
|
63
|
+
*/
|
|
64
|
+
get currentName(): string {
|
|
52
65
|
return this._name;
|
|
53
66
|
}
|
|
54
67
|
|
|
55
68
|
/**
|
|
56
69
|
* Get the current completer of the completion provider.
|
|
57
70
|
*/
|
|
58
|
-
get
|
|
59
|
-
if (this._name ===
|
|
71
|
+
get currentCompleter(): IBaseCompleter | null {
|
|
72
|
+
if (this._name === 'None') {
|
|
60
73
|
return null;
|
|
61
74
|
}
|
|
62
|
-
return this.
|
|
75
|
+
return this._completer;
|
|
63
76
|
}
|
|
64
77
|
|
|
65
78
|
/**
|
|
66
79
|
* Get the current llm chat model.
|
|
67
80
|
*/
|
|
68
|
-
get
|
|
69
|
-
if (this._name ===
|
|
81
|
+
get currentChatModel(): BaseChatModel | null {
|
|
82
|
+
if (this._name === 'None') {
|
|
70
83
|
return null;
|
|
71
84
|
}
|
|
72
|
-
return this.
|
|
85
|
+
return this._chatModel;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get the settings schema of a given provider.
|
|
90
|
+
*/
|
|
91
|
+
getSettingsSchema(provider: string): JSONSchema7 {
|
|
92
|
+
return (this._providers.get(provider)?.settingsSchema?.properties ||
|
|
93
|
+
{}) as JSONSchema7;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get the instructions of a given provider.
|
|
98
|
+
*/
|
|
99
|
+
getInstructions(provider: string): string | undefined {
|
|
100
|
+
return this._providers.get(provider)?.instructions;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Format an error message from the current provider.
|
|
105
|
+
*/
|
|
106
|
+
formatErrorMessage(error: any): string {
|
|
107
|
+
if (this._currentProvider?.errorMessage) {
|
|
108
|
+
return this._currentProvider?.errorMessage(error);
|
|
109
|
+
}
|
|
110
|
+
if (error.message) {
|
|
111
|
+
return error.message;
|
|
112
|
+
}
|
|
113
|
+
return error;
|
|
73
114
|
}
|
|
74
115
|
|
|
75
116
|
/**
|
|
@@ -87,43 +128,59 @@ export class AIProvider implements IAIProvider {
|
|
|
87
128
|
}
|
|
88
129
|
|
|
89
130
|
/**
|
|
90
|
-
* Set the
|
|
91
|
-
* Creates the
|
|
131
|
+
* Set the providers (chat model and completer).
|
|
132
|
+
* Creates the providers if the name has changed, otherwise only updates their config.
|
|
92
133
|
*
|
|
93
|
-
* @param name - the name of the
|
|
134
|
+
* @param name - the name of the provider to use.
|
|
94
135
|
* @param settings - the settings for the models.
|
|
95
136
|
*/
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
137
|
+
setProvider(name: string, settings: ReadonlyPartialJSONObject): void {
|
|
138
|
+
this._currentProvider = this._providers.get(name) ?? null;
|
|
139
|
+
|
|
140
|
+
if (this._currentProvider?.completer !== undefined) {
|
|
141
|
+
try {
|
|
142
|
+
this._completer = new this._currentProvider.completer({ ...settings });
|
|
143
|
+
this._completerError = '';
|
|
144
|
+
} catch (e: any) {
|
|
145
|
+
this._completerError = e.message;
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
this._completer = null;
|
|
102
149
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
150
|
+
|
|
151
|
+
if (this._currentProvider?.chatModel !== undefined) {
|
|
152
|
+
try {
|
|
153
|
+
this._chatModel = new this._currentProvider.chatModel({ ...settings });
|
|
154
|
+
this._chatError = '';
|
|
155
|
+
} catch (e: any) {
|
|
156
|
+
this._chatError = e.message;
|
|
157
|
+
this._chatModel = null;
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
this._chatModel = null;
|
|
109
161
|
}
|
|
110
162
|
this._name = name;
|
|
111
|
-
this.
|
|
163
|
+
this._providerChanged.emit();
|
|
112
164
|
}
|
|
113
165
|
|
|
114
|
-
|
|
115
|
-
|
|
166
|
+
/**
|
|
167
|
+
* A signal emitting when the provider or its settings has changed.
|
|
168
|
+
*/
|
|
169
|
+
get providerChanged(): ISignal<IAIProviderRegistry, void> {
|
|
170
|
+
return this._providerChanged;
|
|
116
171
|
}
|
|
117
172
|
|
|
118
|
-
private
|
|
119
|
-
private
|
|
173
|
+
private _currentProvider: IAIProvider | null = null;
|
|
174
|
+
private _completer: IBaseCompleter | null = null;
|
|
175
|
+
private _chatModel: BaseChatModel | null = null;
|
|
120
176
|
private _name: string = 'None';
|
|
121
|
-
private
|
|
177
|
+
private _providerChanged = new Signal<IAIProviderRegistry, void>(this);
|
|
122
178
|
private _chatError: string = '';
|
|
123
179
|
private _completerError: string = '';
|
|
180
|
+
private _providers = new Map<string, IAIProvider>();
|
|
124
181
|
}
|
|
125
182
|
|
|
126
|
-
export namespace
|
|
183
|
+
export namespace AIProviderRegistry {
|
|
127
184
|
/**
|
|
128
185
|
* The options for the LLM provider.
|
|
129
186
|
*/
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { IDict } from '../tokens';
|
|
2
|
+
|
|
3
|
+
const chromeAiInstructions = `
|
|
4
|
+
<i class="fas fa-exclamation-triangle"></i> Support for ChromeAI is still experimental and only available in Google Chrome.
|
|
5
|
+
|
|
6
|
+
You can test ChromeAI is enabled in your browser by going to the following URL: https://chromeai.org/
|
|
7
|
+
|
|
8
|
+
Enable the proper flags in Google Chrome.
|
|
9
|
+
|
|
10
|
+
- chrome://flags/#prompt-api-for-gemini-nano
|
|
11
|
+
- Select: \`Enabled\`
|
|
12
|
+
- chrome://flags/#optimization-guide-on-device-model
|
|
13
|
+
- Select: \`Enabled BypassPrefRequirement\`
|
|
14
|
+
- chrome://components
|
|
15
|
+
- Click \`Check for Update\` on Optimization Guide On Device Model to download the model
|
|
16
|
+
- [Optional] chrome://flags/#text-safety-classifier
|
|
17
|
+
|
|
18
|
+
<img src="https://github.com/user-attachments/assets/d48f46cc-52ee-4ce5-9eaf-c763cdbee04c" alt="A screenshot showing how to enable the ChromeAI flag in Google Chrome" width="500px">
|
|
19
|
+
|
|
20
|
+
Then restart Chrome for these changes to take effect.
|
|
21
|
+
|
|
22
|
+
<i class="fas fa-exclamation-triangle"></i> On first use, Chrome will download the on-device model, which can be as large as 22GB (according to their docs and at the time of writing).
|
|
23
|
+
During the download, ChromeAI may not be available via the extension.
|
|
24
|
+
|
|
25
|
+
<i class="fa fa-info-circle" aria-hidden="true"></i> For more information about Chrome Built-in AI: https://developer.chrome.com/docs/ai/get-started
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const mistralAIInstructions = `
|
|
29
|
+
<i class="fas fa-exclamation-triangle"></i> This extension is still very much experimental. It is not an official MistralAI extension.
|
|
30
|
+
|
|
31
|
+
1. Go to https://console.mistral.ai/api-keys/ and create an API key.
|
|
32
|
+
|
|
33
|
+
<img src="https://raw.githubusercontent.com/jupyterlite/ai/refs/heads/main/img/1-api-key.png" alt="Screenshot showing how to create an API key" width="500px">
|
|
34
|
+
|
|
35
|
+
2. Open the JupyterLab settings and go to the **Ai providers** section to select the \`MistralAI\`
|
|
36
|
+
provider and the API key (required).
|
|
37
|
+
|
|
38
|
+
<img src="https://raw.githubusercontent.com/jupyterlite/ai/refs/heads/main/img/2-jupyterlab-settings.png" alt="Screenshot showing how to add the API key to the settings" width="500px">
|
|
39
|
+
|
|
40
|
+
3. Open the chat, or use the inline completer
|
|
41
|
+
|
|
42
|
+
<img src="https://raw.githubusercontent.com/jupyterlite/ai/refs/heads/main/img/3-usage.png" alt="Screenshot showing how to use the chat" width="500px">
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
export const instructions: IDict = {
|
|
46
|
+
ChromeAI: chromeAiInstructions,
|
|
47
|
+
MistralAI: mistralAIInstructions
|
|
48
|
+
};
|