Skip to content

Commit ba99ad6

Browse files
authored
Anthropic (Claude) provider (#22)
* Add Anthropic (Claude) chat provider * Bump package * Add code completer based on Anthropic Chat * Update system message * Workaround to build settings for Anthropic * lint * Update README
1 parent 2d040f4 commit ba99ad6

File tree

9 files changed

+295
-11
lines changed

9 files changed

+295
-11
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ The process is different for each provider, so you may refer to their documentat
5353

5454
![Screenshot showing how to create an API key](./img/1-api-key.png)
5555

56-
2. Open the JupyterLab settings and go to the **Ai providers** section to select the provider
57-
(`mistral` is only supported one currently) and the API key (required).
56+
2. Open the JupyterLab settings and go to the **Ai providers** section to select the `MistralAI`
57+
provider and the API key (required).
5858

5959
![Screenshot showing how to add the API key to the settings](./img/2-jupyterlab-settings.png)
6060

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"@jupyterlab/notebook": "^4.4.0-alpha.0",
6262
"@jupyterlab/rendermime": "^4.4.0-alpha.0",
6363
"@jupyterlab/settingregistry": "^4.4.0-alpha.0",
64+
"@langchain/anthropic": "^0.3.9",
6465
"@langchain/core": "^0.3.13",
6566
"@langchain/mistralai": "^0.1.1",
6667
"@lumino/coreutils": "^2.1.2",

schema/ai-provider.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"title": "The AI provider",
99
"description": "The AI provider to use for chat and completion",
1010
"default": "None",
11-
"enum": ["None", "MistralAI"]
11+
"enum": ["None", "Anthropic", "MistralAI"]
1212
}
1313
},
1414
"additionalProperties": true

scripts/settings-generator.js

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,57 @@ const schemaBase = tsj
2020
.createGenerator(configBase)
2121
.createSchema(configBase.type);
2222

23+
/**
24+
* The providers are the list of providers for which we'd like to build settings from their interface.
25+
* The keys will be the names of the json files that will be linked to the selected provider.
26+
* The values are:
27+
* - path: path of the module containing the provider input description, in @langchain package.
28+
* - type: the type or interface to format to json settings.
29+
* - excludedProps: (optional) the properties to not include in the settings.
30+
* "ts-json-schema-generator" seems to not handle some imported types, so the workaround is
31+
* to exclude them at the moment, to be able to build other settings.
32+
*/
2333
const providers = {
2434
mistralAI: {
2535
path: 'node_modules/@langchain/mistralai/dist/chat_models.d.ts',
2636
type: 'ChatMistralAIInput'
37+
},
38+
anthropic: {
39+
path: 'node_modules/@langchain/anthropic/dist/chat_models.d.ts',
40+
type: 'AnthropicInput',
41+
excludedProps: ['clientOptions']
2742
}
2843
};
2944

3045
Object.entries(providers).forEach(([name, desc], index) => {
46+
// The configuration doesn't include functions, which may probably not be filled
47+
// from the settings panel.
3148
const config = {
3249
path: desc.path,
3350
tsconfig: './tsconfig.json',
34-
type: desc.type
51+
type: desc.type,
52+
functions: 'hide'
3553
};
3654

3755
const outputPath = path.join(outputDir, `${name}.json`);
3856

39-
const schema = tsj.createGenerator(config).createSchema(config.type);
57+
const generator = tsj.createGenerator(config);
58+
let schema;
59+
60+
// Workaround to exclude some properties from a type or interface.
61+
if (desc.excludedProps) {
62+
const nodes = generator.getRootNodes(config.type);
63+
const finalMembers = [];
64+
nodes[0].members.forEach(member => {
65+
if (!desc.excludedProps.includes(member.symbol.escapedName)) {
66+
finalMembers.push(member);
67+
}
68+
});
69+
nodes[0].members = finalMembers;
70+
schema = generator.createSchemaFromNodes(nodes);
71+
} else {
72+
schema = generator.createSchema(config.type);
73+
}
4074

4175
if (!schema.definitions) {
4276
return;

src/completion-provider.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import {
33
IInlineCompletionContext,
44
IInlineCompletionProvider
55
} from '@jupyterlab/completer';
6-
import { LLM } from '@langchain/core/language_models/llms';
6+
import { BaseLanguageModel } from '@langchain/core/language_models/base';
7+
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
78

89
import { getCompleter, IBaseCompleter, BaseCompleter } from './llm-models';
9-
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
1010

1111
/**
1212
* The generic completion provider to register to the completion provider manager.
@@ -57,7 +57,7 @@ export class CompletionProvider implements IInlineCompletionProvider {
5757
/**
5858
* Get the LLM completer.
5959
*/
60-
get llmCompleter(): LLM | null {
60+
get llmCompleter(): BaseLanguageModel | null {
6161
return this._completer?.provider || null;
6262
}
6363

src/llm-models/anthropic-completer.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import {
2+
CompletionHandler,
3+
IInlineCompletionContext
4+
} from '@jupyterlab/completer';
5+
import { ChatAnthropic } from '@langchain/anthropic';
6+
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
7+
import { AIMessage, SystemMessage } from '@langchain/core/messages';
8+
9+
import { BaseCompleter, IBaseCompleter } from './base-completer';
10+
11+
export class AnthropicCompleter implements IBaseCompleter {
12+
constructor(options: BaseCompleter.IOptions) {
13+
this._anthropicProvider = new ChatAnthropic({ ...options.settings });
14+
}
15+
16+
get provider(): BaseChatModel {
17+
return this._anthropicProvider;
18+
}
19+
20+
async fetch(
21+
request: CompletionHandler.IRequest,
22+
context: IInlineCompletionContext
23+
) {
24+
const { text, offset: cursorOffset } = request;
25+
const prompt = text.slice(0, cursorOffset);
26+
27+
// Anthropic does not allow whitespace at the end of the AIMessage
28+
const trimmedPrompt = prompt.trim();
29+
30+
const messages = [
31+
new SystemMessage(
32+
'You are a code-completion AI completing the following code from a Jupyter Notebook cell.'
33+
),
34+
new AIMessage(trimmedPrompt)
35+
];
36+
37+
try {
38+
const response = await this._anthropicProvider.invoke(messages);
39+
const items = [];
40+
41+
// Anthropic can return string or complex content, a list of string/images/other.
42+
if (typeof response.content === 'string') {
43+
items.push({
44+
insertText: response.content
45+
});
46+
} else {
47+
response.content.forEach(content => {
48+
if (content.type !== 'text') {
49+
return;
50+
}
51+
items.push({
52+
insertText: content.text,
53+
filterText: prompt.substring(trimmedPrompt.length)
54+
});
55+
});
56+
}
57+
return { items };
58+
} catch (error) {
59+
console.error('Error fetching completions', error);
60+
return { items: [] };
61+
}
62+
}
63+
64+
private _anthropicProvider: ChatAnthropic;
65+
}

src/llm-models/base-completer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import {
22
CompletionHandler,
33
IInlineCompletionContext
44
} from '@jupyterlab/completer';
5-
import { LLM } from '@langchain/core/language_models/llms';
5+
import { BaseLanguageModel } from '@langchain/core/language_models/base';
66
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
77

88
export interface IBaseCompleter {
99
/**
1010
* The LLM completer.
1111
*/
12-
provider: LLM;
12+
provider: BaseLanguageModel;
1313

1414
/**
1515
* The function to fetch a new completion.

src/llm-models/utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
import { ChatAnthropic } from '@langchain/anthropic';
12
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
23
import { ChatMistralAI } from '@langchain/mistralai';
34
import { JSONObject } from '@lumino/coreutils';
45

56
import { IBaseCompleter } from './base-completer';
7+
import { AnthropicCompleter } from './anthropic-completer';
68
import { CodestralCompleter } from './codestral-completer';
79
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
810

911
import mistralAI from '../_provider-settings/mistralAI.json';
12+
import anthropic from '../_provider-settings/anthropic.json';
1013

1114
/**
1215
* Get an LLM completer from the name.
@@ -17,6 +20,8 @@ export function getCompleter(
1720
): IBaseCompleter | null {
1821
if (name === 'MistralAI') {
1922
return new CodestralCompleter({ settings });
23+
} else if (name === 'Anthropic') {
24+
return new AnthropicCompleter({ settings });
2025
}
2126
return null;
2227
}
@@ -30,6 +35,8 @@ export function getChatModel(
3035
): BaseChatModel | null {
3136
if (name === 'MistralAI') {
3237
return new ChatMistralAI({ ...settings });
38+
} else if (name === 'Anthropic') {
39+
return new ChatAnthropic({ ...settings });
3340
}
3441
return null;
3542
}
@@ -40,6 +47,8 @@ export function getChatModel(
4047
export function getErrorMessage(name: string, error: any): string {
4148
if (name === 'MistralAI') {
4249
return error.message;
50+
} else if (name === 'Anthropic') {
51+
return error.error.error.message;
4352
}
4453
return 'Unknown provider';
4554
}
@@ -50,6 +59,8 @@ export function getErrorMessage(name: string, error: any): string {
5059
export function getSettings(name: string): JSONObject | null {
5160
if (name === 'MistralAI') {
5261
return mistralAI.definitions.ChatMistralAIInput.properties;
62+
} else if (name === 'Anthropic') {
63+
return anthropic.definitions.AnthropicInput.properties;
5364
}
5465
return null;
5566
}

0 commit comments

Comments
 (0)