@jupyterlite/ai 0.2.0 → 0.3.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/README.md +48 -9
- package/lib/_provider-settings/anthropic.json +70 -0
- package/lib/_provider-settings/chromeAI.json +21 -0
- package/lib/_provider-settings/mistralAI.json +75 -0
- package/lib/_provider-settings/openAI.json +668 -0
- package/lib/chat-handler.d.ts +12 -0
- package/lib/chat-handler.js +70 -21
- package/lib/completion-provider.d.ts +3 -3
- package/lib/icons.d.ts +2 -0
- package/lib/icons.js +15 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +61 -6
- package/lib/llm-models/anthropic-completer.d.ts +19 -0
- package/lib/llm-models/anthropic-completer.js +57 -0
- package/lib/llm-models/base-completer.d.ts +6 -2
- package/lib/llm-models/chrome-completer.d.ts +19 -0
- package/lib/llm-models/chrome-completer.js +67 -0
- package/lib/llm-models/codestral-completer.d.ts +9 -8
- package/lib/llm-models/codestral-completer.js +37 -54
- package/lib/llm-models/openai-completer.d.ts +19 -0
- package/lib/llm-models/openai-completer.js +51 -0
- package/lib/llm-models/utils.d.ts +1 -0
- package/lib/llm-models/utils.js +57 -0
- package/lib/provider.d.ts +11 -0
- package/lib/provider.js +26 -0
- package/lib/slash-commands.d.ts +16 -0
- package/lib/slash-commands.js +25 -0
- package/package.json +23 -104
- package/schema/ai-provider.json +4 -8
- package/schema/chat.json +8 -0
- package/src/chat-handler.ts +91 -34
- package/src/completion-provider.ts +3 -3
- package/src/icons.ts +18 -0
- package/src/index.ts +67 -5
- package/src/llm-models/anthropic-completer.ts +75 -0
- package/src/llm-models/base-completer.ts +7 -2
- package/src/llm-models/chrome-completer.ts +88 -0
- package/src/llm-models/codestral-completer.ts +43 -69
- package/src/llm-models/openai-completer.ts +67 -0
- package/src/llm-models/svg.d.ts +9 -0
- package/src/llm-models/utils.ts +49 -0
- package/src/provider.ts +38 -0
- package/src/slash-commands.tsx +55 -0
- package/style/icons/jupyternaut-lite.svg +7 -0
package/lib/chat-handler.d.ts
CHANGED
|
@@ -8,11 +8,23 @@ export type ConnectionMessage = {
|
|
|
8
8
|
export declare class ChatHandler extends ChatModel {
|
|
9
9
|
constructor(options: ChatHandler.IOptions);
|
|
10
10
|
get provider(): BaseChatModel | null;
|
|
11
|
+
/**
|
|
12
|
+
* Getter and setter for the persona name.
|
|
13
|
+
*/
|
|
14
|
+
get personaName(): string;
|
|
15
|
+
set personaName(value: string);
|
|
16
|
+
/**
|
|
17
|
+
* Getter and setter for the initial prompt.
|
|
18
|
+
*/
|
|
19
|
+
get prompt(): string;
|
|
20
|
+
set prompt(value: string);
|
|
11
21
|
sendMessage(message: INewMessage): Promise<boolean>;
|
|
12
22
|
getHistory(): Promise<IChatHistory>;
|
|
13
23
|
dispose(): void;
|
|
14
24
|
messageAdded(message: IChatMessage): void;
|
|
15
25
|
private _aiProvider;
|
|
26
|
+
private _personaName;
|
|
27
|
+
private _prompt;
|
|
16
28
|
private _errorMessage;
|
|
17
29
|
private _history;
|
|
18
30
|
private _defaultErrorMessage;
|
package/lib/chat-handler.js
CHANGED
|
@@ -3,28 +3,72 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
import { ChatModel } from '@jupyter/chat';
|
|
6
|
-
import { AIMessage, HumanMessage, mergeMessageRuns } from '@langchain/core/messages';
|
|
6
|
+
import { AIMessage, HumanMessage, mergeMessageRuns, SystemMessage } from '@langchain/core/messages';
|
|
7
7
|
import { UUID } from '@lumino/coreutils';
|
|
8
8
|
import { getErrorMessage } from './llm-models';
|
|
9
|
+
import { chatSystemPrompt } from './provider';
|
|
10
|
+
import { jupyternautLiteIcon } from './icons';
|
|
11
|
+
/**
|
|
12
|
+
* The base64 encoded SVG string of the jupyternaut lite icon.
|
|
13
|
+
* Encode so it can be passed as avatar_url to jupyter-chat.
|
|
14
|
+
*/
|
|
15
|
+
const AI_AVATAR_BASE64 = btoa(jupyternautLiteIcon.svgstr);
|
|
16
|
+
const AI_AVATAR = `data:image/svg+xml;base64,${AI_AVATAR_BASE64}`;
|
|
9
17
|
export class ChatHandler extends ChatModel {
|
|
10
18
|
constructor(options) {
|
|
11
19
|
super(options);
|
|
20
|
+
this._personaName = 'AI';
|
|
12
21
|
this._errorMessage = '';
|
|
13
22
|
this._history = { messages: [] };
|
|
14
23
|
this._defaultErrorMessage = 'AI provider not configured';
|
|
15
24
|
this._aiProvider = options.aiProvider;
|
|
25
|
+
this._prompt = chatSystemPrompt({ provider_name: this._aiProvider.name });
|
|
16
26
|
this._aiProvider.modelChange.connect(() => {
|
|
17
27
|
this._errorMessage = this._aiProvider.chatError;
|
|
28
|
+
this._prompt = chatSystemPrompt({ provider_name: this._aiProvider.name });
|
|
18
29
|
});
|
|
19
30
|
}
|
|
20
31
|
get provider() {
|
|
21
32
|
return this._aiProvider.chatModel;
|
|
22
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Getter and setter for the persona name.
|
|
36
|
+
*/
|
|
37
|
+
get personaName() {
|
|
38
|
+
return this._personaName;
|
|
39
|
+
}
|
|
40
|
+
set personaName(value) {
|
|
41
|
+
this.messages.forEach(message => {
|
|
42
|
+
if (message.sender.username === this._personaName) {
|
|
43
|
+
const updated = { ...message };
|
|
44
|
+
updated.sender.username = value;
|
|
45
|
+
this.messageAdded(updated);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
this._personaName = value;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Getter and setter for the initial prompt.
|
|
52
|
+
*/
|
|
53
|
+
get prompt() {
|
|
54
|
+
return this._prompt;
|
|
55
|
+
}
|
|
56
|
+
set prompt(value) {
|
|
57
|
+
this._prompt = value;
|
|
58
|
+
}
|
|
23
59
|
async sendMessage(message) {
|
|
60
|
+
var _a;
|
|
61
|
+
const body = message.body;
|
|
62
|
+
if (body.startsWith('/clear')) {
|
|
63
|
+
// TODO: do we need a clear method?
|
|
64
|
+
this.messagesDeleted(0, this.messages.length);
|
|
65
|
+
this._history.messages = [];
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
24
68
|
message.id = UUID.uuid4();
|
|
25
69
|
const msg = {
|
|
26
70
|
id: message.id,
|
|
27
|
-
body
|
|
71
|
+
body,
|
|
28
72
|
sender: { username: 'User' },
|
|
29
73
|
time: Date.now(),
|
|
30
74
|
type: 'msg'
|
|
@@ -42,29 +86,34 @@ export class ChatHandler extends ChatModel {
|
|
|
42
86
|
return false;
|
|
43
87
|
}
|
|
44
88
|
this._history.messages.push(msg);
|
|
45
|
-
const messages = mergeMessageRuns(this.
|
|
89
|
+
const messages = mergeMessageRuns([new SystemMessage(this._prompt)]);
|
|
90
|
+
messages.push(...this._history.messages.map(msg => {
|
|
46
91
|
if (msg.sender.username === 'User') {
|
|
47
92
|
return new HumanMessage(msg.body);
|
|
48
93
|
}
|
|
49
94
|
return new AIMessage(msg.body);
|
|
50
95
|
}));
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
this.
|
|
96
|
+
const sender = { username: this._personaName, avatar_url: AI_AVATAR };
|
|
97
|
+
this.updateWriters([sender]);
|
|
98
|
+
// create an empty message to be filled by the AI provider
|
|
99
|
+
const botMsg = {
|
|
100
|
+
id: UUID.uuid4(),
|
|
101
|
+
body: '',
|
|
102
|
+
sender,
|
|
103
|
+
time: Date.now(),
|
|
104
|
+
type: 'msg'
|
|
105
|
+
};
|
|
106
|
+
let content = '';
|
|
107
|
+
try {
|
|
108
|
+
for await (const chunk of await this._aiProvider.chatModel.stream(messages)) {
|
|
109
|
+
content += (_a = chunk.content) !== null && _a !== void 0 ? _a : chunk;
|
|
110
|
+
botMsg.body = content;
|
|
111
|
+
this.messageAdded(botMsg);
|
|
112
|
+
}
|
|
64
113
|
this._history.messages.push(botMsg);
|
|
65
114
|
return true;
|
|
66
|
-
}
|
|
67
|
-
|
|
115
|
+
}
|
|
116
|
+
catch (reason) {
|
|
68
117
|
const error = getErrorMessage(this._aiProvider.name, reason);
|
|
69
118
|
const errorMsg = {
|
|
70
119
|
id: UUID.uuid4(),
|
|
@@ -75,10 +124,10 @@ export class ChatHandler extends ChatModel {
|
|
|
75
124
|
};
|
|
76
125
|
this.messageAdded(errorMsg);
|
|
77
126
|
return false;
|
|
78
|
-
}
|
|
79
|
-
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
80
129
|
this.updateWriters([]);
|
|
81
|
-
}
|
|
130
|
+
}
|
|
82
131
|
}
|
|
83
132
|
async getHistory() {
|
|
84
133
|
return this._history;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CompletionHandler, IInlineCompletionContext, IInlineCompletionProvider } from '@jupyterlab/completer';
|
|
2
|
-
import {
|
|
3
|
-
import { IBaseCompleter, BaseCompleter } from './llm-models';
|
|
2
|
+
import { BaseLanguageModel } from '@langchain/core/language_models/base';
|
|
4
3
|
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
4
|
+
import { IBaseCompleter, BaseCompleter } from './llm-models';
|
|
5
5
|
/**
|
|
6
6
|
* The generic completion provider to register to the completion provider manager.
|
|
7
7
|
*/
|
|
@@ -26,7 +26,7 @@ export declare class CompletionProvider implements IInlineCompletionProvider {
|
|
|
26
26
|
/**
|
|
27
27
|
* Get the LLM completer.
|
|
28
28
|
*/
|
|
29
|
-
get llmCompleter():
|
|
29
|
+
get llmCompleter(): BaseLanguageModel | null;
|
|
30
30
|
fetch(request: CompletionHandler.IRequest, context: IInlineCompletionContext): Promise<any>;
|
|
31
31
|
private _name;
|
|
32
32
|
private _requestCompletion;
|
package/lib/icons.d.ts
ADDED
package/lib/icons.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
6
|
+
/**
|
|
7
|
+
* This icon is based on the jupyternaut icon from Jupyter AI:
|
|
8
|
+
* https://github.com/jupyterlab/jupyter-ai/blob/main/packages/jupyter-ai/style/icons/jupyternaut.svg
|
|
9
|
+
* With a small tweak for the colors to match the JupyterLite icon.
|
|
10
|
+
*/
|
|
11
|
+
import jupyternautLiteSvg from '../style/icons/jupyternaut-lite.svg';
|
|
12
|
+
export const jupyternautLiteIcon = new LabIcon({
|
|
13
|
+
name: '@jupyterlite/ai:jupyternaut-lite',
|
|
14
|
+
svgstr: jupyternautLiteSvg
|
|
15
|
+
});
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import { IAutocompletionRegistry } from '@jupyter/chat';
|
|
1
2
|
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
2
3
|
import { IAIProvider } from './token';
|
|
3
|
-
declare const _default: (JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<IAIProvider>)[];
|
|
4
|
+
declare const _default: (JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<IAutocompletionRegistry> | JupyterFrontEndPlugin<IAIProvider>)[];
|
|
4
5
|
export default _default;
|
package/lib/index.js
CHANGED
|
@@ -1,19 +1,46 @@
|
|
|
1
|
-
import { ActiveCellManager, buildChatSidebar, buildErrorWidget } from '@jupyter/chat';
|
|
1
|
+
import { ActiveCellManager, AutocompletionRegistry, buildChatSidebar, buildErrorWidget, IAutocompletionRegistry } from '@jupyter/chat';
|
|
2
2
|
import { IThemeManager } from '@jupyterlab/apputils';
|
|
3
3
|
import { ICompletionProviderManager } from '@jupyterlab/completer';
|
|
4
4
|
import { INotebookTracker } from '@jupyterlab/notebook';
|
|
5
5
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
6
6
|
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
7
7
|
import { ChatHandler } from './chat-handler';
|
|
8
|
+
import { getSettings } from './llm-models';
|
|
8
9
|
import { AIProvider } from './provider';
|
|
10
|
+
import { renderSlashCommandOption } from './slash-commands';
|
|
9
11
|
import { IAIProvider } from './token';
|
|
12
|
+
const autocompletionRegistryPlugin = {
|
|
13
|
+
id: '@jupyterlite/ai:autocompletion-registry',
|
|
14
|
+
description: 'Autocompletion registry',
|
|
15
|
+
autoStart: true,
|
|
16
|
+
provides: IAutocompletionRegistry,
|
|
17
|
+
activate: () => {
|
|
18
|
+
const autocompletionRegistry = new AutocompletionRegistry();
|
|
19
|
+
const options = ['/clear'];
|
|
20
|
+
const autocompletionCommands = {
|
|
21
|
+
opener: '/',
|
|
22
|
+
commands: options.map(option => {
|
|
23
|
+
return {
|
|
24
|
+
id: option.slice(1),
|
|
25
|
+
label: option,
|
|
26
|
+
description: 'Clear the chat window'
|
|
27
|
+
};
|
|
28
|
+
}),
|
|
29
|
+
props: {
|
|
30
|
+
renderOption: renderSlashCommandOption
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
autocompletionRegistry.add('jupyterlite-ai', autocompletionCommands);
|
|
34
|
+
return autocompletionRegistry;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
10
37
|
const chatPlugin = {
|
|
11
38
|
id: '@jupyterlite/ai:chat',
|
|
12
39
|
description: 'LLM chat extension',
|
|
13
40
|
autoStart: true,
|
|
41
|
+
requires: [IAIProvider, IRenderMimeRegistry, IAutocompletionRegistry],
|
|
14
42
|
optional: [INotebookTracker, ISettingRegistry, IThemeManager],
|
|
15
|
-
|
|
16
|
-
activate: async (app, aiProvider, rmRegistry, notebookTracker, settingsRegistry, themeManager) => {
|
|
43
|
+
activate: async (app, aiProvider, rmRegistry, autocompletionRegistry, notebookTracker, settingsRegistry, themeManager) => {
|
|
17
44
|
let activeCellManager = null;
|
|
18
45
|
if (notebookTracker) {
|
|
19
46
|
activeCellManager = new ActiveCellManager({
|
|
@@ -27,11 +54,15 @@ const chatPlugin = {
|
|
|
27
54
|
});
|
|
28
55
|
let sendWithShiftEnter = false;
|
|
29
56
|
let enableCodeToolbar = true;
|
|
57
|
+
let personaName = 'AI';
|
|
30
58
|
function loadSetting(setting) {
|
|
31
59
|
sendWithShiftEnter = setting.get('sendWithShiftEnter')
|
|
32
60
|
.composite;
|
|
33
61
|
enableCodeToolbar = setting.get('enableCodeToolbar').composite;
|
|
62
|
+
personaName = setting.get('personaName').composite;
|
|
63
|
+
// set the properties
|
|
34
64
|
chatHandler.config = { sendWithShiftEnter, enableCodeToolbar };
|
|
65
|
+
chatHandler.personaName = personaName;
|
|
35
66
|
}
|
|
36
67
|
Promise.all([app.restored, settingsRegistry === null || settingsRegistry === void 0 ? void 0 : settingsRegistry.load(chatPlugin.id)])
|
|
37
68
|
.then(([, settings]) => {
|
|
@@ -50,9 +81,10 @@ const chatPlugin = {
|
|
|
50
81
|
chatWidget = buildChatSidebar({
|
|
51
82
|
model: chatHandler,
|
|
52
83
|
themeManager,
|
|
53
|
-
rmRegistry
|
|
84
|
+
rmRegistry,
|
|
85
|
+
autocompletionRegistry
|
|
54
86
|
});
|
|
55
|
-
chatWidget.title.caption = '
|
|
87
|
+
chatWidget.title.caption = 'Jupyterlite AI Chat';
|
|
56
88
|
}
|
|
57
89
|
catch (e) {
|
|
58
90
|
chatWidget = buildErrorWidget(themeManager);
|
|
@@ -71,11 +103,34 @@ const aiProviderPlugin = {
|
|
|
71
103
|
completionProviderManager: manager,
|
|
72
104
|
requestCompletion: () => app.commands.execute('inline-completer:invoke')
|
|
73
105
|
});
|
|
106
|
+
let currentProvider = 'None';
|
|
74
107
|
settingRegistry
|
|
75
108
|
.load(aiProviderPlugin.id)
|
|
76
109
|
.then(settings => {
|
|
77
110
|
const updateProvider = () => {
|
|
78
111
|
const provider = settings.get('provider').composite;
|
|
112
|
+
if (provider !== currentProvider) {
|
|
113
|
+
// Update the settings panel.
|
|
114
|
+
currentProvider = provider;
|
|
115
|
+
const settingsProperties = settings.schema.properties;
|
|
116
|
+
if (settingsProperties) {
|
|
117
|
+
const schemaKeys = Object.keys(settingsProperties);
|
|
118
|
+
schemaKeys.forEach(key => {
|
|
119
|
+
var _a;
|
|
120
|
+
if (key !== 'provider') {
|
|
121
|
+
(_a = settings.schema.properties) === null || _a === void 0 ? true : delete _a[key];
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
const properties = getSettings(provider);
|
|
125
|
+
if (properties === null) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
Object.entries(properties).forEach(([name, value], index) => {
|
|
129
|
+
settingsProperties[name] = value;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Update the settings to the AI providers.
|
|
79
134
|
aiProvider.setModels(provider, settings.composite);
|
|
80
135
|
};
|
|
81
136
|
settings.changed.connect(() => updateProvider());
|
|
@@ -87,4 +142,4 @@ const aiProviderPlugin = {
|
|
|
87
142
|
return aiProvider;
|
|
88
143
|
}
|
|
89
144
|
};
|
|
90
|
-
export default [chatPlugin, aiProviderPlugin];
|
|
145
|
+
export default [chatPlugin, autocompletionRegistryPlugin, aiProviderPlugin];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CompletionHandler, IInlineCompletionContext } from '@jupyterlab/completer';
|
|
2
|
+
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
3
|
+
import { BaseCompleter, IBaseCompleter } from './base-completer';
|
|
4
|
+
export declare class AnthropicCompleter implements IBaseCompleter {
|
|
5
|
+
constructor(options: BaseCompleter.IOptions);
|
|
6
|
+
get provider(): BaseChatModel;
|
|
7
|
+
/**
|
|
8
|
+
* Getter and setter for the initial prompt.
|
|
9
|
+
*/
|
|
10
|
+
get prompt(): string;
|
|
11
|
+
set prompt(value: string);
|
|
12
|
+
fetch(request: CompletionHandler.IRequest, context: IInlineCompletionContext): Promise<{
|
|
13
|
+
items: {
|
|
14
|
+
insertText: string;
|
|
15
|
+
}[];
|
|
16
|
+
}>;
|
|
17
|
+
private _anthropicProvider;
|
|
18
|
+
private _prompt;
|
|
19
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ChatAnthropic } from '@langchain/anthropic';
|
|
2
|
+
import { AIMessage, SystemMessage } from '@langchain/core/messages';
|
|
3
|
+
import { COMPLETION_SYSTEM_PROMPT } from '../provider';
|
|
4
|
+
export class AnthropicCompleter {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this._prompt = COMPLETION_SYSTEM_PROMPT;
|
|
7
|
+
this._anthropicProvider = new ChatAnthropic({ ...options.settings });
|
|
8
|
+
}
|
|
9
|
+
get provider() {
|
|
10
|
+
return this._anthropicProvider;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Getter and setter for the initial prompt.
|
|
14
|
+
*/
|
|
15
|
+
get prompt() {
|
|
16
|
+
return this._prompt;
|
|
17
|
+
}
|
|
18
|
+
set prompt(value) {
|
|
19
|
+
this._prompt = value;
|
|
20
|
+
}
|
|
21
|
+
async fetch(request, context) {
|
|
22
|
+
const { text, offset: cursorOffset } = request;
|
|
23
|
+
const prompt = text.slice(0, cursorOffset);
|
|
24
|
+
// Anthropic does not allow whitespace at the end of the AIMessage
|
|
25
|
+
const trimmedPrompt = prompt.trim();
|
|
26
|
+
const messages = [
|
|
27
|
+
new SystemMessage(this._prompt),
|
|
28
|
+
new AIMessage(trimmedPrompt)
|
|
29
|
+
];
|
|
30
|
+
try {
|
|
31
|
+
const response = await this._anthropicProvider.invoke(messages);
|
|
32
|
+
const items = [];
|
|
33
|
+
// Anthropic can return string or complex content, a list of string/images/other.
|
|
34
|
+
if (typeof response.content === 'string') {
|
|
35
|
+
items.push({
|
|
36
|
+
insertText: response.content
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
response.content.forEach(content => {
|
|
41
|
+
if (content.type !== 'text') {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
items.push({
|
|
45
|
+
insertText: content.text,
|
|
46
|
+
filterText: prompt.substring(trimmedPrompt.length)
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return { items };
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.error('Error fetching completions', error);
|
|
54
|
+
return { items: [] };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { CompletionHandler, IInlineCompletionContext } from '@jupyterlab/completer';
|
|
2
|
-
import {
|
|
2
|
+
import { BaseLanguageModel } from '@langchain/core/language_models/base';
|
|
3
3
|
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
4
4
|
export interface IBaseCompleter {
|
|
5
5
|
/**
|
|
6
6
|
* The LLM completer.
|
|
7
7
|
*/
|
|
8
|
-
provider:
|
|
8
|
+
provider: BaseLanguageModel;
|
|
9
|
+
/**
|
|
10
|
+
* The completion prompt.
|
|
11
|
+
*/
|
|
12
|
+
prompt: string;
|
|
9
13
|
/**
|
|
10
14
|
* The function to fetch a new completion.
|
|
11
15
|
*/
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CompletionHandler, IInlineCompletionContext } from '@jupyterlab/completer';
|
|
2
|
+
import { LLM } from '@langchain/core/language_models/llms';
|
|
3
|
+
import { BaseCompleter, IBaseCompleter } from './base-completer';
|
|
4
|
+
export declare class ChromeCompleter implements IBaseCompleter {
|
|
5
|
+
constructor(options: BaseCompleter.IOptions);
|
|
6
|
+
/**
|
|
7
|
+
* Getter and setter for the initial prompt.
|
|
8
|
+
*/
|
|
9
|
+
get prompt(): string;
|
|
10
|
+
set prompt(value: string);
|
|
11
|
+
get provider(): LLM;
|
|
12
|
+
fetch(request: CompletionHandler.IRequest, context: IInlineCompletionContext): Promise<{
|
|
13
|
+
items: {
|
|
14
|
+
insertText: string;
|
|
15
|
+
}[];
|
|
16
|
+
}>;
|
|
17
|
+
private _chromeProvider;
|
|
18
|
+
private _prompt;
|
|
19
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ChromeAI } from '@langchain/community/experimental/llms/chrome_ai';
|
|
2
|
+
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
|
|
3
|
+
import { COMPLETION_SYSTEM_PROMPT } from '../provider';
|
|
4
|
+
/**
|
|
5
|
+
* Regular expression to match the '```' string at the start of a string.
|
|
6
|
+
* So the completions returned by the LLM can still be kept after removing the code block formatting.
|
|
7
|
+
*
|
|
8
|
+
* For example, if the response contains the following content after typing `import pandas`:
|
|
9
|
+
*
|
|
10
|
+
* ```python
|
|
11
|
+
* as pd
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* The formatting string after removing the code block delimiters will be:
|
|
15
|
+
*
|
|
16
|
+
* as pd
|
|
17
|
+
*/
|
|
18
|
+
const CODE_BLOCK_START_REGEX = /^```(?:[a-zA-Z]+)?\n?/;
|
|
19
|
+
/**
|
|
20
|
+
* Regular expression to match the '```' string at the end of a string.
|
|
21
|
+
*/
|
|
22
|
+
const CODE_BLOCK_END_REGEX = /```$/;
|
|
23
|
+
export class ChromeCompleter {
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this._prompt = COMPLETION_SYSTEM_PROMPT;
|
|
26
|
+
this._chromeProvider = new ChromeAI({ ...options.settings });
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Getter and setter for the initial prompt.
|
|
30
|
+
*/
|
|
31
|
+
get prompt() {
|
|
32
|
+
return this._prompt;
|
|
33
|
+
}
|
|
34
|
+
set prompt(value) {
|
|
35
|
+
this._prompt = value;
|
|
36
|
+
}
|
|
37
|
+
get provider() {
|
|
38
|
+
return this._chromeProvider;
|
|
39
|
+
}
|
|
40
|
+
async fetch(request, context) {
|
|
41
|
+
const { text, offset: cursorOffset } = request;
|
|
42
|
+
const prompt = text.slice(0, cursorOffset);
|
|
43
|
+
const trimmedPrompt = prompt.trim();
|
|
44
|
+
const messages = [
|
|
45
|
+
new SystemMessage(this._prompt),
|
|
46
|
+
new HumanMessage(trimmedPrompt)
|
|
47
|
+
];
|
|
48
|
+
try {
|
|
49
|
+
let response = await this._chromeProvider.invoke(messages);
|
|
50
|
+
// ChromeAI sometimes returns a string starting with '```',
|
|
51
|
+
// so process the response to remove the code block delimiters
|
|
52
|
+
if (CODE_BLOCK_START_REGEX.test(response)) {
|
|
53
|
+
response = response
|
|
54
|
+
.replace(CODE_BLOCK_START_REGEX, '')
|
|
55
|
+
.replace(CODE_BLOCK_END_REGEX, '');
|
|
56
|
+
}
|
|
57
|
+
const items = [{ insertText: response }];
|
|
58
|
+
return {
|
|
59
|
+
items
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.error('Error fetching completion:', error);
|
|
64
|
+
return { items: [] };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { CompletionHandler, IInlineCompletionContext } from '@jupyterlab/completer';
|
|
2
|
-
import {
|
|
2
|
+
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
3
3
|
import { BaseCompleter, IBaseCompleter } from './base-completer';
|
|
4
4
|
export declare class CodestralCompleter implements IBaseCompleter {
|
|
5
5
|
constructor(options: BaseCompleter.IOptions);
|
|
6
|
-
get provider():
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
get provider(): BaseChatModel;
|
|
7
|
+
/**
|
|
8
|
+
* Getter and setter for the initial prompt.
|
|
9
|
+
*/
|
|
10
|
+
get prompt(): string;
|
|
11
|
+
set prompt(value: string);
|
|
12
|
+
fetch(request: CompletionHandler.IRequest, context: IInlineCompletionContext): Promise<any>;
|
|
12
13
|
private _throttler;
|
|
13
14
|
private _mistralProvider;
|
|
14
|
-
private
|
|
15
|
+
private _prompt;
|
|
15
16
|
}
|
|
@@ -1,75 +1,58 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
|
|
2
|
+
import { ChatMistralAI } from '@langchain/mistralai';
|
|
2
3
|
import { Throttler } from '@lumino/polling';
|
|
4
|
+
import { COMPLETION_SYSTEM_PROMPT } from '../provider';
|
|
3
5
|
/**
|
|
4
6
|
* The Mistral API has a rate limit of 1 request per second
|
|
5
7
|
*/
|
|
6
8
|
const INTERVAL = 1000;
|
|
7
|
-
/**
|
|
8
|
-
* Timeout to avoid endless requests
|
|
9
|
-
*/
|
|
10
|
-
const REQUEST_TIMEOUT = 3000;
|
|
11
9
|
export class CodestralCompleter {
|
|
12
10
|
constructor(options) {
|
|
13
|
-
this.
|
|
14
|
-
|
|
15
|
-
this.
|
|
16
|
-
|
|
17
|
-
var _a;
|
|
18
|
-
const invokedData = data;
|
|
19
|
-
// Request completion.
|
|
20
|
-
const request = this._mistralProvider.completionWithRetry(data, {}, false);
|
|
21
|
-
const timeoutPromise = new Promise(resolve => {
|
|
22
|
-
return setTimeout(() => resolve(null), REQUEST_TIMEOUT);
|
|
23
|
-
});
|
|
24
|
-
// Fetch again if the request is too long or if the prompt has changed.
|
|
25
|
-
const response = await Promise.race([request, timeoutPromise]);
|
|
26
|
-
if (response === null ||
|
|
27
|
-
invokedData.prompt !== ((_a = this._currentData) === null || _a === void 0 ? void 0 : _a.prompt)) {
|
|
28
|
-
return {
|
|
29
|
-
items: [],
|
|
30
|
-
fetchAgain: true
|
|
31
|
-
};
|
|
32
|
-
}
|
|
11
|
+
this._prompt = COMPLETION_SYSTEM_PROMPT;
|
|
12
|
+
this._mistralProvider = new ChatMistralAI({ ...options.settings });
|
|
13
|
+
this._throttler = new Throttler(async (messages) => {
|
|
14
|
+
const response = await this._mistralProvider.invoke(messages);
|
|
33
15
|
// Extract results of completion request.
|
|
34
|
-
const items =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
16
|
+
const items = [];
|
|
17
|
+
if (typeof response.content === 'string') {
|
|
18
|
+
items.push({
|
|
19
|
+
insertText: response.content
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
response.content.forEach(content => {
|
|
24
|
+
if (content.type !== 'text') {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
items.push({
|
|
28
|
+
insertText: content.text
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return { items };
|
|
40
33
|
}, { limit: INTERVAL });
|
|
41
34
|
}
|
|
42
35
|
get provider() {
|
|
43
36
|
return this._mistralProvider;
|
|
44
37
|
}
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Getter and setter for the initial prompt.
|
|
40
|
+
*/
|
|
41
|
+
get prompt() {
|
|
42
|
+
return this._prompt;
|
|
43
|
+
}
|
|
44
|
+
set prompt(value) {
|
|
45
|
+
this._prompt = value;
|
|
47
46
|
}
|
|
48
47
|
async fetch(request, context) {
|
|
49
48
|
const { text, offset: cursorOffset } = request;
|
|
50
49
|
const prompt = text.slice(0, cursorOffset);
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
prompt
|
|
54
|
-
|
|
55
|
-
model: this._mistralProvider.model,
|
|
56
|
-
// temperature: 0,
|
|
57
|
-
// top_p: 1,
|
|
58
|
-
// max_tokens: 1024,
|
|
59
|
-
// min_tokens: 0,
|
|
60
|
-
stream: false,
|
|
61
|
-
// random_seed: 1337,
|
|
62
|
-
stop: []
|
|
63
|
-
};
|
|
50
|
+
const messages = [
|
|
51
|
+
new SystemMessage(this._prompt),
|
|
52
|
+
new HumanMessage(prompt)
|
|
53
|
+
];
|
|
64
54
|
try {
|
|
65
|
-
this.
|
|
66
|
-
const completionResult = await this._throttler.invoke(data);
|
|
67
|
-
if (completionResult.fetchAgain) {
|
|
68
|
-
if (this._requestCompletion) {
|
|
69
|
-
this._requestCompletion();
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return { items: completionResult.items };
|
|
55
|
+
return await this._throttler.invoke(messages);
|
|
73
56
|
}
|
|
74
57
|
catch (error) {
|
|
75
58
|
console.error('Error fetching completions', error);
|