@jupyterlite/ai 0.4.0 → 0.6.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 +9 -1
- package/lib/chat-handler.js +37 -1
- package/lib/completion-provider.d.ts +8 -1
- package/lib/components/stop-button.d.ts +19 -0
- package/lib/components/stop-button.js +32 -0
- package/lib/{llm-models/anthropic-completer.d.ts → default-providers/Anthropic/completer.d.ts} +1 -1
- package/lib/{llm-models/anthropic-completer.js → default-providers/Anthropic/completer.js} +1 -1
- package/lib/{llm-models/chrome-completer.d.ts → default-providers/ChromeAI/completer.d.ts} +1 -1
- package/lib/{llm-models/chrome-completer.js → default-providers/ChromeAI/completer.js} +1 -1
- package/lib/default-providers/ChromeAI/instructions.d.ts +2 -0
- package/lib/default-providers/ChromeAI/instructions.js +24 -0
- package/lib/{llm-models/codestral-completer.d.ts → default-providers/MistralAI/completer.d.ts} +1 -1
- package/lib/{llm-models/codestral-completer.js → default-providers/MistralAI/completer.js} +1 -1
- package/lib/default-providers/MistralAI/instructions.d.ts +2 -0
- package/lib/default-providers/MistralAI/instructions.js +16 -0
- package/lib/{llm-models/openai-completer.d.ts → default-providers/OpenAI/completer.d.ts} +1 -1
- package/lib/{llm-models/openai-completer.js → default-providers/OpenAI/completer.js} +1 -1
- package/lib/default-providers/index.d.ts +2 -0
- package/lib/default-providers/index.js +60 -0
- package/lib/index.d.ts +3 -2
- package/lib/index.js +57 -36
- package/lib/provider.d.ts +13 -12
- package/lib/provider.js +43 -9
- package/lib/settings/index.d.ts +3 -0
- package/lib/settings/index.js +3 -0
- package/lib/settings/panel.d.ts +17 -0
- package/lib/settings/panel.js +92 -5
- package/lib/settings/settings-connector.d.ts +31 -0
- package/lib/settings/settings-connector.js +61 -0
- package/lib/settings/utils.d.ts +3 -0
- package/lib/settings/utils.js +5 -0
- package/lib/tokens.d.ts +16 -4
- package/package.json +14 -7
- package/schema/provider-registry.json +6 -0
- package/src/chat-handler.ts +43 -1
- package/src/completion-provider.ts +8 -1
- package/src/components/stop-button.tsx +56 -0
- package/src/{llm-models/anthropic-completer.ts → default-providers/Anthropic/completer.ts} +2 -2
- package/src/{llm-models/chrome-completer.ts → default-providers/ChromeAI/completer.ts} +3 -2
- package/src/default-providers/ChromeAI/instructions.ts +24 -0
- package/src/{llm-models/codestral-completer.ts → default-providers/MistralAI/completer.ts} +2 -2
- package/src/default-providers/MistralAI/instructions.ts +16 -0
- package/src/{llm-models/openai-completer.ts → default-providers/OpenAI/completer.ts} +2 -2
- package/src/default-providers/index.ts +71 -0
- package/src/index.ts +77 -49
- package/src/provider.ts +58 -15
- package/src/settings/index.ts +3 -0
- package/src/settings/panel.tsx +109 -5
- package/src/settings/settings-connector.ts +89 -0
- package/src/settings/utils.ts +6 -0
- package/src/tokens.ts +17 -4
- package/lib/llm-models/index.d.ts +0 -4
- package/lib/llm-models/index.js +0 -43
- package/lib/settings/instructions.d.ts +0 -2
- package/lib/settings/instructions.js +0 -44
- package/lib/settings/schemas/index.d.ts +0 -3
- package/lib/settings/schemas/index.js +0 -11
- package/lib/slash-commands.d.ts +0 -16
- package/lib/slash-commands.js +0 -25
- package/src/llm-models/index.ts +0 -50
- package/src/settings/instructions.ts +0 -48
- package/src/settings/schemas/index.ts +0 -15
- package/src/slash-commands.tsx +0 -55
- /package/lib/{llm-models/base-completer.d.ts → base-completer.d.ts} +0 -0
- /package/lib/{llm-models/base-completer.js → base-completer.js} +0 -0
- /package/lib/{settings/schemas/_generated/Anthropic.json → default-providers/Anthropic/settings-schema.json} +0 -0
- /package/lib/{settings/schemas/_generated/ChromeAI.json → default-providers/ChromeAI/settings-schema.json} +0 -0
- /package/lib/{settings/schemas/_generated/MistralAI.json → default-providers/MistralAI/settings-schema.json} +0 -0
- /package/lib/{settings/schemas/_generated/OpenAI.json → default-providers/OpenAI/settings-schema.json} +0 -0
- /package/lib/settings/{schemas/base.json → base.json} +0 -0
- /package/src/{llm-models/base-completer.ts → base-completer.ts} +0 -0
- /package/src/{llm-models/svg.d.ts → global.d.ts} +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import StopIcon from '@mui/icons-material/Stop';
|
|
7
|
+
import React from 'react';
|
|
8
|
+
|
|
9
|
+
import { InputToolbarRegistry, TooltippedButton } from '@jupyter/chat';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Properties of the stop button.
|
|
13
|
+
*/
|
|
14
|
+
export interface IStopButtonProps
|
|
15
|
+
extends InputToolbarRegistry.IToolbarItemProps {
|
|
16
|
+
/**
|
|
17
|
+
* The function to stop streaming.
|
|
18
|
+
*/
|
|
19
|
+
stopStreaming: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The stop button.
|
|
24
|
+
*/
|
|
25
|
+
export function StopButton(props: IStopButtonProps): JSX.Element {
|
|
26
|
+
const tooltip = 'Stop streaming';
|
|
27
|
+
return (
|
|
28
|
+
<TooltippedButton
|
|
29
|
+
onClick={props.stopStreaming}
|
|
30
|
+
tooltip={tooltip}
|
|
31
|
+
buttonProps={{
|
|
32
|
+
size: 'small',
|
|
33
|
+
variant: 'contained',
|
|
34
|
+
title: tooltip
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
<StopIcon />
|
|
38
|
+
</TooltippedButton>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* factory returning the toolbar item.
|
|
44
|
+
*/
|
|
45
|
+
export function stopItem(
|
|
46
|
+
stopStreaming: () => void
|
|
47
|
+
): InputToolbarRegistry.IToolbarItem {
|
|
48
|
+
return {
|
|
49
|
+
element: (props: InputToolbarRegistry.IToolbarItemProps) => {
|
|
50
|
+
const stopProps: IStopButtonProps = { ...props, stopStreaming };
|
|
51
|
+
return StopButton(stopProps);
|
|
52
|
+
},
|
|
53
|
+
position: 50,
|
|
54
|
+
hidden: true /* hidden by default */
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -6,8 +6,8 @@ import { ChatAnthropic } from '@langchain/anthropic';
|
|
|
6
6
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
7
7
|
import { AIMessage, SystemMessage } from '@langchain/core/messages';
|
|
8
8
|
|
|
9
|
-
import { BaseCompleter, IBaseCompleter } from '
|
|
10
|
-
import { COMPLETION_SYSTEM_PROMPT } from '
|
|
9
|
+
import { BaseCompleter, IBaseCompleter } from '../../base-completer';
|
|
10
|
+
import { COMPLETION_SYSTEM_PROMPT } from '../../provider';
|
|
11
11
|
|
|
12
12
|
export class AnthropicCompleter implements IBaseCompleter {
|
|
13
13
|
constructor(options: BaseCompleter.IOptions) {
|
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
import { ChromeAI } from '@langchain/community/experimental/llms/chrome_ai';
|
|
6
6
|
import { LLM } from '@langchain/core/language_models/llms';
|
|
7
7
|
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
|
|
8
|
-
|
|
9
|
-
import {
|
|
8
|
+
|
|
9
|
+
import { BaseCompleter, IBaseCompleter } from '../../base-completer';
|
|
10
|
+
import { COMPLETION_SYSTEM_PROMPT } from '../../provider';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Regular expression to match the '```' string at the start of a string.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export default `
|
|
2
|
+
<i class="fas fa-exclamation-triangle"></i> Support for ChromeAI is still experimental and only available in Google Chrome.
|
|
3
|
+
|
|
4
|
+
You can test ChromeAI is enabled in your browser by going to the following URL: <https://chromeai.org/>
|
|
5
|
+
|
|
6
|
+
Enable the proper flags in Google Chrome.
|
|
7
|
+
|
|
8
|
+
- chrome://flags/#prompt-api-for-gemini-nano
|
|
9
|
+
- Select: \`Enabled\`
|
|
10
|
+
- chrome://flags/#optimization-guide-on-device-model
|
|
11
|
+
- Select: \`Enabled BypassPrefRequirement\`
|
|
12
|
+
- chrome://components
|
|
13
|
+
- Click \`Check for Update\` on Optimization Guide On Device Model to download the model
|
|
14
|
+
- [Optional] chrome://flags/#text-safety-classifier
|
|
15
|
+
|
|
16
|
+
<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">
|
|
17
|
+
|
|
18
|
+
Then restart Chrome for these changes to take effect.
|
|
19
|
+
|
|
20
|
+
<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).
|
|
21
|
+
During the download, ChromeAI may not be available via the extension.
|
|
22
|
+
|
|
23
|
+
<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>
|
|
24
|
+
`;
|
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
import { ChatMistralAI } from '@langchain/mistralai';
|
|
12
12
|
import { Throttler } from '@lumino/polling';
|
|
13
13
|
|
|
14
|
-
import { BaseCompleter, IBaseCompleter } from '
|
|
15
|
-
import { COMPLETION_SYSTEM_PROMPT } from '
|
|
14
|
+
import { BaseCompleter, IBaseCompleter } from '../../base-completer';
|
|
15
|
+
import { COMPLETION_SYSTEM_PROMPT } from '../../provider';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* The Mistral API has a rate limit of 1 request per second
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default `
|
|
2
|
+
<i class="fas fa-exclamation-triangle"></i> This extension is still very much experimental. It is not an official MistralAI extension.
|
|
3
|
+
|
|
4
|
+
1. Go to <https://console.mistral.ai/api-keys/> and create an API key.
|
|
5
|
+
|
|
6
|
+
<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">
|
|
7
|
+
|
|
8
|
+
2. Open the JupyterLab settings and go to the **Ai providers** section to select the \`MistralAI\`
|
|
9
|
+
provider and the API key (required).
|
|
10
|
+
|
|
11
|
+
<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">
|
|
12
|
+
|
|
13
|
+
3. Open the chat, or use the inline completer
|
|
14
|
+
|
|
15
|
+
<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">
|
|
16
|
+
`;
|
|
@@ -6,8 +6,8 @@ import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
|
6
6
|
import { AIMessage, SystemMessage } from '@langchain/core/messages';
|
|
7
7
|
import { ChatOpenAI } from '@langchain/openai';
|
|
8
8
|
|
|
9
|
-
import { BaseCompleter, IBaseCompleter } from '
|
|
10
|
-
import { COMPLETION_SYSTEM_PROMPT } from '
|
|
9
|
+
import { BaseCompleter, IBaseCompleter } from '../../base-completer';
|
|
10
|
+
import { COMPLETION_SYSTEM_PROMPT } from '../../provider';
|
|
11
11
|
|
|
12
12
|
export class OpenAICompleter implements IBaseCompleter {
|
|
13
13
|
constructor(options: BaseCompleter.IOptions) {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
JupyterFrontEnd,
|
|
3
|
+
JupyterFrontEndPlugin
|
|
4
|
+
} from '@jupyterlab/application';
|
|
5
|
+
import { ChatAnthropic } from '@langchain/anthropic';
|
|
6
|
+
import { ChromeAI } from '@langchain/community/experimental/llms/chrome_ai';
|
|
7
|
+
import { ChatMistralAI } from '@langchain/mistralai';
|
|
8
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
9
|
+
|
|
10
|
+
import { IAIProvider, IAIProviderRegistry } from '../tokens';
|
|
11
|
+
|
|
12
|
+
// Import completers
|
|
13
|
+
import { AnthropicCompleter } from './Anthropic/completer';
|
|
14
|
+
import { ChromeCompleter } from './ChromeAI/completer';
|
|
15
|
+
import { CodestralCompleter } from './MistralAI/completer';
|
|
16
|
+
import { OpenAICompleter } from './OpenAI/completer';
|
|
17
|
+
|
|
18
|
+
// Import Settings
|
|
19
|
+
import AnthropicSettings from './Anthropic/settings-schema.json';
|
|
20
|
+
import ChromeAISettings from './ChromeAI/settings-schema.json';
|
|
21
|
+
import MistralAISettings from './MistralAI/settings-schema.json';
|
|
22
|
+
import OpenAISettings from './OpenAI/settings-schema.json';
|
|
23
|
+
|
|
24
|
+
// Import instructions
|
|
25
|
+
import ChromeAIInstructions from './ChromeAI/instructions';
|
|
26
|
+
import MistralAIInstructions from './MistralAI/instructions';
|
|
27
|
+
|
|
28
|
+
// Build the AIProvider list
|
|
29
|
+
const AIProviders: IAIProvider[] = [
|
|
30
|
+
{
|
|
31
|
+
name: 'Anthropic',
|
|
32
|
+
chatModel: ChatAnthropic,
|
|
33
|
+
completer: AnthropicCompleter,
|
|
34
|
+
settingsSchema: AnthropicSettings,
|
|
35
|
+
errorMessage: (error: any) => error.error.error.message
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'ChromeAI',
|
|
39
|
+
// TODO: fix
|
|
40
|
+
// @ts-expect-error: missing properties
|
|
41
|
+
chatModel: ChromeAI,
|
|
42
|
+
completer: ChromeCompleter,
|
|
43
|
+
instructions: ChromeAIInstructions,
|
|
44
|
+
settingsSchema: ChromeAISettings
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'MistralAI',
|
|
48
|
+
chatModel: ChatMistralAI,
|
|
49
|
+
completer: CodestralCompleter,
|
|
50
|
+
instructions: MistralAIInstructions,
|
|
51
|
+
settingsSchema: MistralAISettings
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'OpenAI',
|
|
55
|
+
chatModel: ChatOpenAI,
|
|
56
|
+
completer: OpenAICompleter,
|
|
57
|
+
settingsSchema: OpenAISettings
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
export const defaultProviderPlugins: JupyterFrontEndPlugin<void>[] =
|
|
62
|
+
AIProviders.map(provider => {
|
|
63
|
+
return {
|
|
64
|
+
id: `@jupyterlite/ai:${provider.name}`,
|
|
65
|
+
autoStart: true,
|
|
66
|
+
requires: [IAIProviderRegistry],
|
|
67
|
+
activate: (app: JupyterFrontEnd, registry: IAIProviderRegistry) => {
|
|
68
|
+
registry.add(provider);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ActiveCellManager,
|
|
3
|
-
AutocompletionRegistry,
|
|
4
3
|
buildChatSidebar,
|
|
5
4
|
buildErrorWidget,
|
|
5
|
+
ChatCommandRegistry,
|
|
6
6
|
IActiveCellManager,
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
IChatCommandRegistry,
|
|
8
|
+
InputToolbarRegistry
|
|
9
9
|
} from '@jupyter/chat';
|
|
10
10
|
import {
|
|
11
11
|
JupyterFrontEnd,
|
|
@@ -15,56 +15,45 @@ import { ReactWidget, IThemeManager } from '@jupyterlab/apputils';
|
|
|
15
15
|
import { ICompletionProviderManager } from '@jupyterlab/completer';
|
|
16
16
|
import { INotebookTracker } from '@jupyterlab/notebook';
|
|
17
17
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
ISettingConnector,
|
|
20
|
+
ISettingRegistry
|
|
21
|
+
} from '@jupyterlab/settingregistry';
|
|
19
22
|
import { IFormRendererRegistry } from '@jupyterlab/ui-components';
|
|
20
23
|
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
24
|
+
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
21
25
|
|
|
22
26
|
import { ChatHandler } from './chat-handler';
|
|
23
27
|
import { CompletionProvider } from './completion-provider';
|
|
24
|
-
import {
|
|
28
|
+
import { defaultProviderPlugins } from './default-providers';
|
|
25
29
|
import { AIProviderRegistry } from './provider';
|
|
26
|
-
import { aiSettingsRenderer } from './settings
|
|
27
|
-
import { renderSlashCommandOption } from './slash-commands';
|
|
30
|
+
import { aiSettingsRenderer, SettingConnector } from './settings';
|
|
28
31
|
import { IAIProviderRegistry } from './tokens';
|
|
32
|
+
import { stopItem } from './components/stop-button';
|
|
29
33
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
commands: options.map(option => {
|
|
42
|
-
return {
|
|
43
|
-
id: option.slice(1),
|
|
44
|
-
label: option,
|
|
45
|
-
description: 'Clear the chat window'
|
|
46
|
-
};
|
|
47
|
-
}),
|
|
48
|
-
props: {
|
|
49
|
-
renderOption: renderSlashCommandOption
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
autocompletionRegistry.add('jupyterlite-ai', autocompletionCommands);
|
|
53
|
-
return autocompletionRegistry;
|
|
54
|
-
}
|
|
55
|
-
};
|
|
34
|
+
const chatCommandRegistryPlugin: JupyterFrontEndPlugin<IChatCommandRegistry> = {
|
|
35
|
+
id: '@jupyterlite/ai:autocompletion-registry',
|
|
36
|
+
description: 'Autocompletion registry',
|
|
37
|
+
autoStart: true,
|
|
38
|
+
provides: IChatCommandRegistry,
|
|
39
|
+
activate: () => {
|
|
40
|
+
const registry = new ChatCommandRegistry();
|
|
41
|
+
registry.addProvider(new ChatHandler.ClearCommandProvider());
|
|
42
|
+
return registry;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
56
45
|
|
|
57
46
|
const chatPlugin: JupyterFrontEndPlugin<void> = {
|
|
58
47
|
id: '@jupyterlite/ai:chat',
|
|
59
48
|
description: 'LLM chat extension',
|
|
60
49
|
autoStart: true,
|
|
61
|
-
requires: [IAIProviderRegistry, IRenderMimeRegistry,
|
|
50
|
+
requires: [IAIProviderRegistry, IRenderMimeRegistry, IChatCommandRegistry],
|
|
62
51
|
optional: [INotebookTracker, ISettingRegistry, IThemeManager],
|
|
63
52
|
activate: async (
|
|
64
53
|
app: JupyterFrontEnd,
|
|
65
54
|
providerRegistry: IAIProviderRegistry,
|
|
66
55
|
rmRegistry: IRenderMimeRegistry,
|
|
67
|
-
|
|
56
|
+
chatCommandRegistry: IChatCommandRegistry,
|
|
68
57
|
notebookTracker: INotebookTracker | null,
|
|
69
58
|
settingsRegistry: ISettingRegistry | null,
|
|
70
59
|
themeManager: IThemeManager | null
|
|
@@ -115,12 +104,30 @@ const chatPlugin: JupyterFrontEndPlugin<void> = {
|
|
|
115
104
|
});
|
|
116
105
|
|
|
117
106
|
let chatWidget: ReactWidget | null = null;
|
|
107
|
+
|
|
108
|
+
const inputToolbarRegistry = InputToolbarRegistry.defaultToolbarRegistry();
|
|
109
|
+
const stopButton = stopItem(() => chatHandler.stopStreaming());
|
|
110
|
+
inputToolbarRegistry.addItem('stop', stopButton);
|
|
111
|
+
|
|
112
|
+
chatHandler.writersChanged.connect((_, users) => {
|
|
113
|
+
if (
|
|
114
|
+
users.filter(user => user.username === chatHandler.personaName).length
|
|
115
|
+
) {
|
|
116
|
+
inputToolbarRegistry.hide('send');
|
|
117
|
+
inputToolbarRegistry.show('stop');
|
|
118
|
+
} else {
|
|
119
|
+
inputToolbarRegistry.hide('stop');
|
|
120
|
+
inputToolbarRegistry.show('send');
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
118
124
|
try {
|
|
119
125
|
chatWidget = buildChatSidebar({
|
|
120
126
|
model: chatHandler,
|
|
121
127
|
themeManager,
|
|
122
128
|
rmRegistry,
|
|
123
|
-
|
|
129
|
+
chatCommandRegistry,
|
|
130
|
+
inputToolbarRegistry
|
|
124
131
|
});
|
|
125
132
|
chatWidget.title.caption = 'Jupyterlite AI Chat';
|
|
126
133
|
} catch (e) {
|
|
@@ -154,20 +161,28 @@ const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
|
|
|
154
161
|
id: '@jupyterlite/ai:provider-registry',
|
|
155
162
|
autoStart: true,
|
|
156
163
|
requires: [IFormRendererRegistry, ISettingRegistry],
|
|
157
|
-
optional: [IRenderMimeRegistry],
|
|
164
|
+
optional: [IRenderMimeRegistry, ISecretsManager, ISettingConnector],
|
|
158
165
|
provides: IAIProviderRegistry,
|
|
159
166
|
activate: (
|
|
160
167
|
app: JupyterFrontEnd,
|
|
161
168
|
editorRegistry: IFormRendererRegistry,
|
|
162
169
|
settingRegistry: ISettingRegistry,
|
|
163
|
-
rmRegistry?: IRenderMimeRegistry
|
|
170
|
+
rmRegistry?: IRenderMimeRegistry,
|
|
171
|
+
secretsManager?: ISecretsManager,
|
|
172
|
+
settingConnector?: ISettingConnector
|
|
164
173
|
): IAIProviderRegistry => {
|
|
165
|
-
const providerRegistry = new AIProviderRegistry();
|
|
174
|
+
const providerRegistry = new AIProviderRegistry({ secretsManager });
|
|
166
175
|
|
|
167
176
|
editorRegistry.addRenderer(
|
|
168
177
|
'@jupyterlite/ai:provider-registry.AIprovider',
|
|
169
|
-
aiSettingsRenderer({
|
|
178
|
+
aiSettingsRenderer({
|
|
179
|
+
providerRegistry,
|
|
180
|
+
rmRegistry,
|
|
181
|
+
secretsManager,
|
|
182
|
+
settingConnector
|
|
183
|
+
})
|
|
170
184
|
);
|
|
185
|
+
|
|
171
186
|
settingRegistry
|
|
172
187
|
.load(providerRegistryPlugin.id)
|
|
173
188
|
.then(settings => {
|
|
@@ -176,10 +191,10 @@ const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
|
|
|
176
191
|
const providerSettings = (settings.get('AIprovider').composite ?? {
|
|
177
192
|
provider: 'None'
|
|
178
193
|
}) as ReadonlyPartialJSONObject;
|
|
179
|
-
providerRegistry.setProvider(
|
|
180
|
-
providerSettings.provider as string,
|
|
181
|
-
providerSettings
|
|
182
|
-
);
|
|
194
|
+
providerRegistry.setProvider({
|
|
195
|
+
name: providerSettings.provider as string,
|
|
196
|
+
settings: providerSettings
|
|
197
|
+
});
|
|
183
198
|
};
|
|
184
199
|
|
|
185
200
|
settings.changed.connect(() => updateProvider());
|
|
@@ -192,16 +207,29 @@ const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
|
|
|
192
207
|
);
|
|
193
208
|
});
|
|
194
209
|
|
|
195
|
-
// Initialize the registry with the default providers
|
|
196
|
-
AIProviders.forEach(provider => providerRegistry.add(provider));
|
|
197
|
-
|
|
198
210
|
return providerRegistry;
|
|
199
211
|
}
|
|
200
212
|
};
|
|
201
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Provides the settings connector as a separate plugin to allow for alternative
|
|
216
|
+
* implementations that may want to fetch settings from a different source or
|
|
217
|
+
* endpoint.
|
|
218
|
+
*/
|
|
219
|
+
const settingsConnector: JupyterFrontEndPlugin<ISettingConnector> = {
|
|
220
|
+
id: '@jupyterlite/ai:settings-connector',
|
|
221
|
+
description: 'Provides a settings connector which does not save passwords.',
|
|
222
|
+
autoStart: true,
|
|
223
|
+
provides: ISettingConnector,
|
|
224
|
+
activate: (app: JupyterFrontEnd) =>
|
|
225
|
+
new SettingConnector(app.serviceManager.settings)
|
|
226
|
+
};
|
|
227
|
+
|
|
202
228
|
export default [
|
|
203
229
|
providerRegistryPlugin,
|
|
204
|
-
|
|
230
|
+
chatCommandRegistryPlugin,
|
|
205
231
|
chatPlugin,
|
|
206
|
-
completerPlugin
|
|
232
|
+
completerPlugin,
|
|
233
|
+
settingsConnector,
|
|
234
|
+
...defaultProviderPlugins
|
|
207
235
|
];
|
package/src/provider.ts
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
import { ICompletionProviderManager } from '@jupyterlab/completer';
|
|
2
1
|
import { BaseLanguageModel } from '@langchain/core/language_models/base';
|
|
3
2
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
4
3
|
import { ISignal, Signal } from '@lumino/signaling';
|
|
5
4
|
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
6
|
-
|
|
7
|
-
import { IBaseCompleter } from './llm-models';
|
|
8
|
-
import { IAIProvider, IAIProviderRegistry } from './tokens';
|
|
9
5
|
import { JSONSchema7 } from 'json-schema';
|
|
6
|
+
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
7
|
+
|
|
8
|
+
import { IBaseCompleter } from './base-completer';
|
|
9
|
+
import {
|
|
10
|
+
getSecretId,
|
|
11
|
+
SECRETS_NAMESPACE,
|
|
12
|
+
SECRETS_REPLACEMENT
|
|
13
|
+
} from './settings';
|
|
14
|
+
import {
|
|
15
|
+
IAIProvider,
|
|
16
|
+
IAIProviderRegistry,
|
|
17
|
+
IDict,
|
|
18
|
+
ISetProviderOptions
|
|
19
|
+
} from './tokens';
|
|
10
20
|
|
|
11
21
|
export const chatSystemPrompt = (
|
|
12
22
|
options: AIProviderRegistry.IPromptOptions
|
|
@@ -39,6 +49,13 @@ Do not include the prompt in the output, only the string that should be appended
|
|
|
39
49
|
`;
|
|
40
50
|
|
|
41
51
|
export class AIProviderRegistry implements IAIProviderRegistry {
|
|
52
|
+
/**
|
|
53
|
+
* The constructor of the provider registry.
|
|
54
|
+
*/
|
|
55
|
+
constructor(options: AIProviderRegistry.IOptions) {
|
|
56
|
+
this._secretsManager = options.secretsManager || null;
|
|
57
|
+
}
|
|
58
|
+
|
|
42
59
|
/**
|
|
43
60
|
* Get the list of provider names.
|
|
44
61
|
*/
|
|
@@ -56,6 +73,11 @@ export class AIProviderRegistry implements IAIProviderRegistry {
|
|
|
56
73
|
);
|
|
57
74
|
}
|
|
58
75
|
this._providers.set(provider.name, provider);
|
|
76
|
+
|
|
77
|
+
// Set the provider if the loading has been deferred.
|
|
78
|
+
if (provider.name === this._deferredProvider?.name) {
|
|
79
|
+
this.setProvider(this._deferredProvider);
|
|
80
|
+
}
|
|
59
81
|
}
|
|
60
82
|
|
|
61
83
|
/**
|
|
@@ -131,15 +153,36 @@ export class AIProviderRegistry implements IAIProviderRegistry {
|
|
|
131
153
|
* Set the providers (chat model and completer).
|
|
132
154
|
* Creates the providers if the name has changed, otherwise only updates their config.
|
|
133
155
|
*
|
|
134
|
-
* @param
|
|
135
|
-
* @param settings - the settings for the models.
|
|
156
|
+
* @param options - An object with the name and the settings of the provider to use.
|
|
136
157
|
*/
|
|
137
|
-
setProvider(
|
|
158
|
+
async setProvider(options: ISetProviderOptions): Promise<void> {
|
|
159
|
+
const { name, settings } = options;
|
|
138
160
|
this._currentProvider = this._providers.get(name) ?? null;
|
|
161
|
+
if (this._currentProvider === null) {
|
|
162
|
+
// The current provider may not be loaded when the settings are first loaded.
|
|
163
|
+
// Let's defer the provider loading.
|
|
164
|
+
this._deferredProvider = options;
|
|
165
|
+
} else {
|
|
166
|
+
this._deferredProvider = null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Build a new settings object containing the secrets.
|
|
170
|
+
const fullSettings: IDict = {};
|
|
171
|
+
for (const key of Object.keys(settings)) {
|
|
172
|
+
if (settings[key] === SECRETS_REPLACEMENT) {
|
|
173
|
+
const id = getSecretId(name, key);
|
|
174
|
+
const secrets = await this._secretsManager?.get(SECRETS_NAMESPACE, id);
|
|
175
|
+
fullSettings[key] = secrets?.value || settings[key];
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
fullSettings[key] = settings[key];
|
|
179
|
+
}
|
|
139
180
|
|
|
140
181
|
if (this._currentProvider?.completer !== undefined) {
|
|
141
182
|
try {
|
|
142
|
-
this._completer = new this._currentProvider.completer({
|
|
183
|
+
this._completer = new this._currentProvider.completer({
|
|
184
|
+
...fullSettings
|
|
185
|
+
});
|
|
143
186
|
this._completerError = '';
|
|
144
187
|
} catch (e: any) {
|
|
145
188
|
this._completerError = e.message;
|
|
@@ -150,7 +193,9 @@ export class AIProviderRegistry implements IAIProviderRegistry {
|
|
|
150
193
|
|
|
151
194
|
if (this._currentProvider?.chatModel !== undefined) {
|
|
152
195
|
try {
|
|
153
|
-
this._chatModel = new this._currentProvider.chatModel({
|
|
196
|
+
this._chatModel = new this._currentProvider.chatModel({
|
|
197
|
+
...fullSettings
|
|
198
|
+
});
|
|
154
199
|
this._chatError = '';
|
|
155
200
|
} catch (e: any) {
|
|
156
201
|
this._chatError = e.message;
|
|
@@ -170,6 +215,7 @@ export class AIProviderRegistry implements IAIProviderRegistry {
|
|
|
170
215
|
return this._providerChanged;
|
|
171
216
|
}
|
|
172
217
|
|
|
218
|
+
private _secretsManager: ISecretsManager | null;
|
|
173
219
|
private _currentProvider: IAIProvider | null = null;
|
|
174
220
|
private _completer: IBaseCompleter | null = null;
|
|
175
221
|
private _chatModel: BaseChatModel | null = null;
|
|
@@ -178,6 +224,7 @@ export class AIProviderRegistry implements IAIProviderRegistry {
|
|
|
178
224
|
private _chatError: string = '';
|
|
179
225
|
private _completerError: string = '';
|
|
180
226
|
private _providers = new Map<string, IAIProvider>();
|
|
227
|
+
private _deferredProvider: ISetProviderOptions | null = null;
|
|
181
228
|
}
|
|
182
229
|
|
|
183
230
|
export namespace AIProviderRegistry {
|
|
@@ -186,13 +233,9 @@ export namespace AIProviderRegistry {
|
|
|
186
233
|
*/
|
|
187
234
|
export interface IOptions {
|
|
188
235
|
/**
|
|
189
|
-
* The
|
|
190
|
-
*/
|
|
191
|
-
completionProviderManager: ICompletionProviderManager;
|
|
192
|
-
/**
|
|
193
|
-
* The application commands registry.
|
|
236
|
+
* The secrets manager used in the application.
|
|
194
237
|
*/
|
|
195
|
-
|
|
238
|
+
secretsManager?: ISecretsManager;
|
|
196
239
|
}
|
|
197
240
|
|
|
198
241
|
/**
|