add LanguageModelChat as explict object and add a select-function as the only way of getting to them

This commit is contained in:
Johannes 2024-05-08 11:44:47 +02:00
parent 3ec5b4a291
commit 0d55bb645f
No known key found for this signature in database
GPG key ID: 6DEF802A22264FCA
8 changed files with 267 additions and 311 deletions

View file

@ -38,8 +38,8 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape {
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatProvider);
this._proxy.$updateLanguageModels({ added: coalesce(_chatProviderService.getLanguageModelIds().map(id => _chatProviderService.lookupLanguageModel(id))) });
this._store.add(_chatProviderService.onDidChangeLanguageModels(this._proxy.$updateLanguageModels, this._proxy));
this._proxy.$acceptChatModelMetadata({ added: coalesce(_chatProviderService.getLanguageModelIds().map(id => _chatProviderService.lookupLanguageModel(id))) });
this._store.add(_chatProviderService.onDidChangeLanguageModels(this._proxy.$acceptChatModelMetadata, this._proxy));
}
dispose(): void {
@ -78,6 +78,10 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape {
this._providerRegistrations.deleteAndDispose(handle);
}
$selectChatModels(selector: Partial<ILanguageModelChatMetadata>): Promise<string[]> {
return this._chatProviderService.selectLanguageModels(selector);
}
$whenLanguageModelChatRequestMade(identifier: string, extensionId: ExtensionIdentifier, participant?: string | undefined, tokenCount?: number | undefined): void {
this._languageModelStatsService.update(identifier, extensionId, participant, tokenCount);
}

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import * as errors from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { combinedDisposable } from 'vs/base/common/lifecycle';
@ -1428,29 +1428,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
// namespace: lm
const lm: typeof vscode.lm = {
get languageModels() {
selectLanguageModels: (selector) => {
checkProposedApiEnabled(extension, 'languageModels');
return extHostLanguageModels.getLanguageModelIds();
return extHostLanguageModels.selectLanguageModels(extension, selector);
},
onDidChangeLanguageModels: (listener, thisArgs?, disposables?) => {
onDidChangeChatModels: (listener, thisArgs?, disposables?) => {
checkProposedApiEnabled(extension, 'languageModels');
return extHostLanguageModels.onDidChangeProviders(listener, thisArgs, disposables);
},
sendChatRequest(languageModel: string, messages: (vscode.LanguageModelChatMessage | vscode.LanguageModelChatMessage2)[], options?: vscode.LanguageModelChatRequestOptions, token?: vscode.CancellationToken) {
checkProposedApiEnabled(extension, 'languageModels');
token ??= CancellationToken.None;
options ??= {};
return extHostLanguageModels.sendChatRequest(extension, languageModel, messages, options, token);
},
computeTokenLength(languageModel: string, text: string | vscode.LanguageModelChatMessage, token?: vscode.CancellationToken) {
checkProposedApiEnabled(extension, 'languageModels');
token ??= CancellationToken.None;
return extHostLanguageModels.computeTokenLength(languageModel, text, token);
},
getLanguageModelInformation(languageModel: string) {
checkProposedApiEnabled(extension, 'languageModels');
return extHostLanguageModels.getLanguageModelInfo(languageModel);
},
// --- embeddings
get embeddingModels() {
checkProposedApiEnabled(extension, 'embeddings');

View file

@ -1201,6 +1201,8 @@ export interface MainThreadLanguageModelsShape extends IDisposable {
$unregisterProvider(handle: number): void;
$handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise<void>;
$selectChatModels(selector: Partial<ILanguageModelChatMetadata>): Promise<string[]>;
$prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise<ILanguageModelChatMetadata | undefined>;
$fetchResponse(extension: ExtensionIdentifier, provider: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise<any>;
@ -1209,7 +1211,7 @@ export interface MainThreadLanguageModelsShape extends IDisposable {
}
export interface ExtHostLanguageModelsShape {
$updateLanguageModels(data: { added?: ILanguageModelChatMetadata[]; removed?: string[] }): void;
$acceptChatModelMetadata(data: { added?: ILanguageModelChatMetadata[]; removed?: string[] }): void;
$updateModelAccesslist(data: { from: ExtensionIdentifier; to: ExtensionIdentifier; enabled: boolean }[]): void;
$provideLanguageModelResponse(handle: number, requestId: number, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise<any>;
$handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise<void>;

View file

@ -3,26 +3,26 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AsyncIterableSource, Barrier } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { CancellationError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Iterable } from 'vs/base/common/iterator';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { Progress } from 'vs/platform/progress/common/progress';
import { ExtHostLanguageModelsShape, MainContext, MainThreadLanguageModelsShape } from 'vs/workbench/api/common/extHost.protocol';
import { IExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
import type * as vscode from 'vscode';
import { Progress } from 'vs/platform/progress/common/progress';
import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels';
import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { AsyncIterableSource, Barrier } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { localize } from 'vs/nls';
import { INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication';
import { CancellationError } from 'vs/base/common/errors';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { IExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication';
import { ILogService } from 'vs/platform/log/common/log';
import { Iterable } from 'vs/base/common/iterator';
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import type * as vscode from 'vscode';
export interface IExtHostLanguageModels extends ExtHostLanguageModels { }
@ -110,7 +110,6 @@ class LanguageModelResponse {
stream.resolve();
}
}
}
export class ExtHostLanguageModels implements ExtHostLanguageModelsShape {
@ -121,11 +120,11 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape {
private readonly _proxy: MainThreadLanguageModelsShape;
private readonly _onDidChangeModelAccess = new Emitter<{ from: ExtensionIdentifier; to: ExtensionIdentifier }>();
private readonly _onDidChangeProviders = new Emitter<vscode.LanguageModelChangeEvent>();
private readonly _onDidChangeProviders = new Emitter<void>();
readonly onDidChangeProviders = this._onDidChangeProviders.event;
private readonly _languageModels = new Map<number, LanguageModelData>();
private readonly _allLanguageModelData = new Map<string, ILanguageModelChatMetadata>(); // these are ALL models, not just the one in this EH
private readonly _allLanguageModelData = new Map<string, { metadata: ILanguageModelChatMetadata; apiObjects: ExtensionIdentifierMap<vscode.LanguageModelChat> }>(); // these are ALL models, not just the one in this EH
private readonly _modelAccessList = new ExtensionIdentifierMap<ExtensionIdentifierSet>();
private readonly _pendingRequest = new Map<number, { languageModelId: string; res: LanguageModelResponse }>();
@ -156,7 +155,9 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape {
this._proxy.$registerLanguageModelProvider(handle, identifier, {
extension: extension.identifier,
identifier: identifier,
vendor: metadata.vendor ?? ExtensionIdentifier.toKey(extension.identifier),
name: metadata.name ?? '',
family: metadata.family ?? '',
version: metadata.version,
tokens: metadata.tokens,
auth
@ -209,12 +210,12 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape {
//#region --- making request
$updateLanguageModels(data: { added?: ILanguageModelChatMetadata[] | undefined; removed?: string[] | undefined }): void {
$acceptChatModelMetadata(data: { added?: ILanguageModelChatMetadata[] | undefined; removed?: string[] | undefined }): void {
const added: string[] = [];
const removed: string[] = [];
if (data.added) {
for (const metadata of data.added) {
this._allLanguageModelData.set(metadata.identifier, metadata);
this._allLanguageModelData.set(metadata.identifier, { metadata, apiObjects: new ExtensionIdentifierMap() });
added.push(metadata.identifier);
}
}
@ -234,39 +235,65 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape {
}
}
this._onDidChangeProviders.fire(Object.freeze({
added: Object.freeze(added),
removed: Object.freeze(removed)
}));
this._onDidChangeProviders.fire(undefined);
// TODO@jrieken@TylerLeonhardt - this is a temporary hack to populate the auth providers
data.added?.forEach(this._fakeAuthPopulate, this);
}
getLanguageModelIds(): string[] {
return Array.from(this._allLanguageModelData.keys());
}
async selectLanguageModels(extension: IExtensionDescription, selector: vscode.LanguageModelChatSelector) {
$updateModelAccesslist(data: { from: ExtensionIdentifier; to: ExtensionIdentifier; enabled: boolean }[]): void {
const updated = new Array<{ from: ExtensionIdentifier; to: ExtensionIdentifier }>();
for (const { from, to, enabled } of data) {
const set = this._modelAccessList.get(from) ?? new ExtensionIdentifierSet();
const oldValue = set.has(to);
if (oldValue !== enabled) {
if (enabled) {
set.add(to);
} else {
set.delete(to);
}
this._modelAccessList.set(from, set);
const newItem = { from, to };
updated.push(newItem);
this._onDidChangeModelAccess.fire(newItem);
// this triggers extension activation
const models = await this._proxy.$selectChatModels(selector);
const result: vscode.LanguageModelChat[] = [];
const that = this;
for (const identifier of models) {
const data = this._allLanguageModelData.get(identifier);
if (!data) {
// model gone? is this an error on us?
continue;
}
let apiObject = data.apiObjects.get(extension.identifier);
if (!apiObject) {
apiObject = {
id: identifier,
vendor: data.metadata.vendor,
family: data.metadata.family,
version: data.metadata.version,
name: data.metadata.name,
contextSize: data.metadata.tokens,
computeTokenLength(text, token) {
if (!that._allLanguageModelData.has(identifier)) {
throw extHostTypes.LanguageModelError.NotFound(identifier);
}
return that._computeTokenLength(identifier, text, token ?? CancellationToken.None);
},
sendRequest(messages, options, token) {
if (!that._allLanguageModelData.has(identifier)) {
throw extHostTypes.LanguageModelError.NotFound(identifier);
}
return that._sendChatRequest(extension, identifier, messages, options ?? {}, token ?? CancellationToken.None);
}
};
Object.freeze(apiObject);
data.apiObjects.set(extension.identifier, apiObject);
}
result.push(apiObject);
}
if (result.length === 0) {
return undefined;
}
return result;
}
async sendChatRequest(extension: IExtensionDescription, languageModelId: string, messages: (vscode.LanguageModelChatMessage | vscode.LanguageModelChatMessage2)[], options: vscode.LanguageModelChatRequestOptions, token: CancellationToken) {
private async _sendChatRequest(extension: IExtensionDescription, languageModelId: string, messages: vscode.LanguageModelChatMessage[], options: vscode.LanguageModelChatRequestOptions, token: CancellationToken) {
const internalMessages: IChatMessage[] = this._convertMessages(extension, messages);
@ -329,17 +356,10 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape {
private _convertMessages(extension: IExtensionDescription, messages: vscode.LanguageModelChatMessage[]) {
const internalMessages: IChatMessage[] = [];
for (const message of messages) {
if (message instanceof extHostTypes.LanguageModelChatMessage) {
if (message.role as number === extHostTypes.LanguageModelChatMessageRole.System) {
checkProposedApiEnabled(extension, 'languageModelSystem');
}
internalMessages.push(typeConvert.LanguageModelChatMessage.from(message));
} else {
if (message instanceof extHostTypes.LanguageModelChatSystemMessage) {
checkProposedApiEnabled(extension, 'languageModelSystem');
}
internalMessages.push(typeConvert.LanguageModelChatMessage.from(message));
if (message.role as number === extHostTypes.LanguageModelChatMessageRole.System) {
checkProposedApiEnabled(extension, 'languageModelSystem');
}
internalMessages.push(typeConvert.LanguageModelChatMessage.from(message));
}
return internalMessages;
}
@ -399,7 +419,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape {
}
}
async computeTokenLength(languageModelId: string, value: string | vscode.LanguageModelChatMessage, token: vscode.CancellationToken): Promise<number> {
private async _computeTokenLength(languageModelId: string, value: string | vscode.LanguageModelChatMessage, token: vscode.CancellationToken): Promise<number> {
const data = this._allLanguageModelData.get(languageModelId);
if (!data) {
@ -412,21 +432,26 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape {
return local.provider.provideTokenCount(value, token);
}
return this._proxy.$countTokens(data.identifier, (typeof value === 'string' ? value : typeConvert.LanguageModelChatMessage.from(value)), token);
return this._proxy.$countTokens(data.metadata.identifier, (typeof value === 'string' ? value : typeConvert.LanguageModelChatMessage.from(value)), token);
}
getLanguageModelInfo(languageModelId: string): vscode.LanguageModelInformation | undefined {
const data = this._allLanguageModelData.get(languageModelId);
if (!data) {
return undefined;
$updateModelAccesslist(data: { from: ExtensionIdentifier; to: ExtensionIdentifier; enabled: boolean }[]): void {
const updated = new Array<{ from: ExtensionIdentifier; to: ExtensionIdentifier }>();
for (const { from, to, enabled } of data) {
const set = this._modelAccessList.get(from) ?? new ExtensionIdentifierSet();
const oldValue = set.has(to);
if (oldValue !== enabled) {
if (enabled) {
set.add(to);
} else {
set.delete(to);
}
this._modelAccessList.set(from, set);
const newItem = { from, to };
updated.push(newItem);
this._onDidChangeModelAccess.fire(newItem);
}
}
return Object.freeze({
id: data.identifier,
name: data.name,
version: data.version,
contextLength: data.tokens,
});
}
private readonly _languageAccessInformationExtensions = new Set<Readonly<IExtensionDescription>>();
@ -449,7 +474,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape {
if (!data) {
return undefined;
}
if (!that._isUsingAuth(from.identifier, data)) {
if (!that._isUsingAuth(from.identifier, data.metadata)) {
return true;
}
@ -457,7 +482,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape {
if (!list) {
return undefined;
}
return list.has(data.extension);
return list.has(data.metadata.extension);
}
};
}

View file

@ -2242,7 +2242,7 @@ export namespace ChatFollowup {
export namespace LanguageModelChatMessage {
export function to(message: chatProvider.IChatMessage): vscode.LanguageModelChatMessage2 {
export function to(message: chatProvider.IChatMessage): vscode.LanguageModelChatMessage {
switch (message.role) {
case chatProvider.ChatMessageRole.System: return new types.LanguageModelChatMessage(<number>types.LanguageModelChatMessageRole.System, message.content);
case chatProvider.ChatMessageRole.User: return new types.LanguageModelChatMessage(<number>types.LanguageModelChatMessageRole.User, message.content);
@ -2250,7 +2250,7 @@ export namespace LanguageModelChatMessage {
}
}
export function from(message: vscode.LanguageModelChatMessage2): chatProvider.IChatMessage {
export function from(message: vscode.LanguageModelChatMessage): chatProvider.IChatMessage {
switch (message.role as types.LanguageModelChatMessageRole) {
case types.LanguageModelChatMessageRole.System: return { role: chatProvider.ChatMessageRole.System, content: message.content };
case types.LanguageModelChatMessageRole.User: return { role: chatProvider.ChatMessageRole.User, content: message.content };

View file

@ -6,9 +6,11 @@
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { isEmptyObject } from 'vs/base/common/types';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IProgress } from 'vs/platform/progress/common/progress';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
export const enum ChatMessageRole {
System,
@ -28,9 +30,11 @@ export interface IChatResponseFragment {
export interface ILanguageModelChatMetadata {
readonly extension: ExtensionIdentifier;
readonly identifier: string;
readonly name: string;
readonly identifier: string;
readonly vendor: string;
readonly version: string;
readonly family: string;
readonly tokens: number;
readonly auth?: {
@ -57,6 +61,8 @@ export interface ILanguageModelsService {
lookupLanguageModel(identifier: string): ILanguageModelChatMetadata | undefined;
selectLanguageModels(selector: Partial<ILanguageModelChatMetadata>): Promise<string[]>;
registerLanguageModelChat(identifier: string, provider: ILanguageModelChat): IDisposable;
makeLanguageModelChatRequest(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress<IChatResponseFragment>, token: CancellationToken): Promise<any>;
@ -72,6 +78,10 @@ export class LanguageModelsService implements ILanguageModelsService {
private readonly _onDidChangeProviders = new Emitter<{ added?: ILanguageModelChatMetadata[]; removed?: string[] }>();
readonly onDidChangeLanguageModels: Event<{ added?: ILanguageModelChatMetadata[]; removed?: string[] }> = this._onDidChangeProviders.event;
constructor(
@IExtensionService private readonly _extensionService: IExtensionService,
) { }
dispose() {
this._onDidChangeProviders.dispose();
this._providers.clear();
@ -85,6 +95,29 @@ export class LanguageModelsService implements ILanguageModelsService {
return this._providers.get(identifier)?.metadata;
}
async selectLanguageModels(selector: Partial<ILanguageModelChatMetadata>): Promise<string[]> {
await this._extensionService.activateByEvent(`onLanguageModelChat:${selector.vendor ?? '*'}}`);
const result: string[] = [];
for (const model of this._providers.values()) {
if (selector.vendor !== undefined && model.metadata.vendor === selector.vendor
|| selector.family !== undefined && model.metadata.family === selector.family
|| selector.version !== undefined && model.metadata.version === selector.version
|| selector.identifier !== undefined && model.metadata.identifier === selector.identifier
) {
// true selection
result.push(model.metadata.identifier);
} else if (!selector || isEmptyObject(selector)) {
// no selection
result.push(model.metadata.identifier);
}
}
return result;
}
registerLanguageModelChat(identifier: string, provider: ILanguageModelChat): IDisposable {
if (this._providers.has(identifier)) {
throw new Error(`Chat response provider with identifier ${identifier} is already registered.`);

View file

@ -19,20 +19,30 @@ declare module 'vscode' {
onDidReceiveLanguageModelResponse2?: Event<{ readonly extensionId: string; readonly participant?: string; readonly tokenCount?: number }>;
provideLanguageModelResponse(messages: LanguageModelChatMessage2[], options: { [name: string]: any }, extensionId: string, progress: Progress<ChatResponseFragment>, token: CancellationToken): Thenable<any>;
provideLanguageModelResponse(messages: LanguageModelChatMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress<ChatResponseFragment>, token: CancellationToken): Thenable<any>;
provideTokenCount(text: string | LanguageModelChatMessage, token: CancellationToken): Thenable<number>;
}
export interface ChatResponseProviderMetadata {
/**
* The name of the model that is used for this chat access. It is expected that the model name can
* be used to lookup properties like token limits and ChatML support
*/
// TODO@API rename to model
name: string;
version: string;
readonly vendor: string;
/**
* Human-readable name of the language model.
*/
readonly name: string;
/**
* Opaque family-name of the language model. Values might be `gpt-3.5-turbo`, `gpt4`, `phi2`, or `llama`
* but they are defined by extensions contributing languages and subject to change.
*/
readonly family: string;
/**
* Opaque version string of the model. This is defined by the extension contributing the language model
* and subject to change while the identifier is stable.
*/
readonly version: string;
tokens: number;

View file

@ -66,12 +66,6 @@ declare module 'vscode' {
Assistant = 2
}
/**
* @deprecated
*/
// TODO@API remove
export type LanguageModelChatMessage2 = LanguageModelChatMessage;
/**
* Represents a message in a chat. Can assume different roles, like user or assistant.
*/
@ -101,179 +95,89 @@ declare module 'vscode' {
constructor(role: LanguageModelChatMessageRole, content: string, name?: string);
}
/**
* Represents information about a registered language model.
*/
export interface LanguageModelInformation {
// ---------------------------
// Language Model Object (V2)
// (+) can pick by id or family
// (++) makes it harder to hardcode an identifier of a model in source code
// TODO@API name LanguageModelChatEndpoint
export interface LanguageModelChat {
/**
* The identifier of the language model.
* Opaque identifier of the language model.
*/
readonly id: string;
/**
* The human-readable name of the language model.
* A well-know identifier of the vendor of the language model, a sample is `copilot`, but
* values are defined by extensions contributing chat model and need to be looked up with them.
*/
readonly vendor: string;
/**
* Human-readable name of the language model.
*/
readonly name: string;
/**
* Opaque family-name of the language model. Values might be `gpt-3.5-turbo`, `gpt4`, `phi2`, or `llama`
* but they are defined by extensions contributing languages and subject to change.
*/
readonly family: string;
/**
* Opaque version string of the model. This is defined by the extension contributing the language model
* and subject to change while the identifier is stable.
*/
readonly version: string;
/**
* Opaque family-name of the language model. Values might be `gpt-3.5-turbe`, `gpt4`, `phi2`, or `llama`
* but they are defined by extensions contributing languages and subject to change.
*/
family: string;
/**
* The number of available tokens that can be used when sending requests
* to the language model.
*
* _Note_ that input- and output-tokens count towards this limit.
*
* @see {@link lm.sendChatRequest}
*/
// TODO@API CAPI only defines prompt_token_count which IMO is just input-tokens
readonly contextLength: number;
}
// ---------------------------
// Language Model Object (V1)
export interface LanguageModelInformation2 {
/**
* Human-readable name of the language model.
*/
name: string;
/**
* Opaque family-name of the language model. Values might be `gpt-3.5-turbe`, `gpt4`, `phi2`, or `llama`
* but they are defined by extensions contributing languages and subject to change.
*/
family: string;
/**
* Opaque version string of the model. This is defined by the extension contributing the language model
* and subject to change while the identifier is stable.
*/
version: string;
}
export interface LanguageModel {
// TODO@API no id-property needed
readonly id: string;
readonly info: LanguageModelInformation2;
sendChatRequest(messages: LanguageModelChatMessage[], options?: LanguageModelChatRequestOptions, token?: CancellationToken): Thenable<LanguageModelChatResponse>;
// maybe optional
computeTokenLength(text: string | LanguageModelChatMessage, token?: CancellationToken): Thenable<number | undefined>;
}
export namespace lm {
// TODO@API cannot enforce unique language model families, e.g how do we tell `openai.gpt4` apart from `copilot.gpt4`
export function getLanguageModel(family: string): LanguageModel[] | undefined;
export const languageModels2: LanguageModel[];
}
// ---------------------------
// ---------------------------
// Language Model Object (V2)
// (+) can pick by id or family
// (++) makes it harder to hardcode an identifier of a model in source code
export interface LanguageModelInformation2 {
/**
* Opaque identifier of the language model.
*/
readonly id: string;
/**
* Human-readable name of the language model.
*/
name: string;
/**
* Opaque family-name of the language model. Values might be `gpt-3.5-turbo`, `gpt4`, `phi2`, or `llama`
* but they are defined by extensions contributing languages and subject to change.
*/
family: string;
/**
* Opaque version string of the model. This is defined by the extension contributing the language model
* and subject to change while the identifier is stable.
*/
version: string;
}
export interface LanguageModel3 {
/**
* Opaque identifier of the language model.
*/
readonly id: string;
vendor?: string;
/**
* Human-readable name of the language model.
*/
name: string;
/**
* Opaque family-name of the language model. Values might be `gpt-3.5-turbo`, `gpt4`, `phi2`, or `llama`
* but they are defined by extensions contributing languages and subject to change.
*/
family: string;
/**
* Opaque version string of the model. This is defined by the extension contributing the language model
* and subject to change while the identifier is stable.
*/
version: string;
// TODO@API
// max_prompt_tokens vs output_tokens vs context_size
contextSize: number;
readonly contextSize: number;
sendChatRequest2(messages: LanguageModelChatMessage[], options?: LanguageModelChatRequestOptions, token?: CancellationToken): Thenable<LanguageModelChatResponse>;
/**
* Make a chat request using a language model.
*
* - *Note 1:* language model use may be subject to access restrictions and user consent. Calling this function
* for the first time (for a extension) will show a consent dialog to the user and because of that this function
* must _only be called in response to a user action!_ Extension can use {@link LanguageModelAccessInformation.canSendRequest}
* to check if they have the necessary permissions to make a request.
*
* - *Note 2:* language models are contributed by other extensions and as they evolve and change,
* the set of available language models may change over time. Therefore it is strongly recommend to check
* {@link languageModels} for available values and handle missing language models gracefully.
*
* This function will return a rejected promise if making a request to the language model is not
* possible. Reasons for this can be:
*
* - user consent not given, see {@link LanguageModelError.NoPermissions `NoPermissions`}
* - model does not exist anymore, see {@link LanguageModelError.NotFound `NotFound`}
* - quota limits exceeded, see {@link LanguageModelError.Blocked `Blocked`}
* - other issues in which case extension must check {@link LanguageModelError.cause `LanguageModelError.cause`}
*
* @param messages An array of message instances.
* @param options Options that control the request.
* @param token A cancellation token which controls the request. See {@link CancellationTokenSource} for how to create one.
* @returns A thenable that resolves to a {@link LanguageModelChatResponse}. The promise will reject when the request couldn't be made.
*/
sendRequest(messages: LanguageModelChatMessage[], options?: LanguageModelChatRequestOptions, token?: CancellationToken): Thenable<LanguageModelChatResponse>;
/**
* Uses the model specific tokenzier and computes the length in tokens of a given message.
*
* @param text A string or a message instance.
* @param token Optional cancellation token.
* @returns A thenable that resolves to the length of the message in tokens.
*/
// TODO@API `undefined` when the language model does not support computing token length
// ollama has nothing
// anthropic suggests to count after the fact https://github.com/anthropics/anthropic-tokenizer-typescript?tab=readme-ov-file#anthropic-typescript-tokenizer
computeTokenLength(text: string | LanguageModelChatMessage, token?: CancellationToken): Thenable<number | undefined>;
}
export namespace lm {
// export const languageModels3: readonly LanguageModelInformation2[];
// export const onDidChangeLanguageModels3: Event<{ readonly added: readonly LanguageModelInformation2[]; readonly removed: readonly LanguageModelInformation2[] }>;
// export const onDidChangeLanguageModels3: Event<void>;
// (++) lazy activation
// (++) give specific LM to some extension
// // variant A
// export function fetchLanguageModel(selector: { id?: string; family?: string; version?: string }): Thenable<LanguageModelInformation2[] | undefined>;
// // variant B
export function fetchLanguageModel(selector: { vendor: string; family?: string; version?: string; id?: string }): Thenable<LanguageModel3[] | undefined>;
// export function sendChatRequest2(languageModel: LanguageModelInformation2, messages: LanguageModelChatMessage[], options?: LanguageModelChatRequestOptions, token?: CancellationToken): Thenable<LanguageModelChatResponse>;
// export function computeTokenLength(languageModel: LanguageModelInformation2, text: string | LanguageModelChatMessage, token?: CancellationToken): Thenable<number | undefined>;
}
// ---------------------------
/**
* An event describing the change in the set of available language models.
*/
// TODO@API use LanguageModelInformation instead of string?
export interface LanguageModelChangeEvent {
/**
* Added language models.
*/
readonly added: readonly string[];
/**
* Removed language models.
*/
readonly removed: readonly string[];
export interface LanguageModelChatSelector {
vendor?: string; // TODO@API make required?
family?: string;
version?: string;
id?: string;
}
/**
@ -337,65 +241,57 @@ declare module 'vscode' {
export namespace lm {
/**
* The identifiers of all language models that are currently available.
* An event that is fired when the set of available chat models changes.
*/
export const languageModels: readonly string[];
export const onDidChangeChatModels: Event<void>;
/**
* An event that is fired when the set of available language models changes.
*/
export const onDidChangeLanguageModels: Event<LanguageModelChangeEvent>;
// // variant B
// (++) lazy activation
// (++) give specific LM to some extension
export function selectLanguageModels(selector: LanguageModelChatSelector): Thenable<LanguageModelChat[] | undefined>;
/**
* Retrieve information about a language model.
*
* @param languageModel A language model identifier.
* @returns A {@link LanguageModelInformation} instance or `undefined` if the language model does not exist.
*/
export function getLanguageModelInformation(languageModel: string): LanguageModelInformation | undefined;
// /**
// * Make a chat request using a language model.
// *
// * - *Note 1:* language model use may be subject to access restrictions and user consent. Calling this function
// * for the first time (for a extension) will show a consent dialog to the user and because of that this function
// * must _only be called in response to a user action!_ Extension can use {@link LanguageModelAccessInformation.canSendRequest}
// * to check if they have the necessary permissions to make a request.
// *
// * - *Note 2:* language models are contributed by other extensions and as they evolve and change,
// * the set of available language models may change over time. Therefore it is strongly recommend to check
// * {@link languageModels} for available values and handle missing language models gracefully.
// *
// * This function will return a rejected promise if making a request to the language model is not
// * possible. Reasons for this can be:
// *
// * - user consent not given, see {@link LanguageModelError.NoPermissions `NoPermissions`}
// * - model does not exist, see {@link LanguageModelError.NotFound `NotFound`}
// * - quota limits exceeded, see {@link LanguageModelError.Blocked `Blocked`}
// * - other issues in which case extension must check {@link LanguageModelError.cause `LanguageModelError.cause`}
// *
// * @param languageModel A language model identifier.
// * @param messages An array of message instances.
// * @param options Options that control the request.
// * @param token A cancellation token which controls the request. See {@link CancellationTokenSource} for how to create one.
// * @returns A thenable that resolves to a {@link LanguageModelChatResponse}. The promise will reject when the request couldn't be made.
// */
// export function sendChatRequest(languageModel: string, messages: LanguageModelChatMessage[], options?: LanguageModelChatRequestOptions, token?: CancellationToken): Thenable<LanguageModelChatResponse>;
/**
* Make a chat request using a language model.
*
* - *Note 1:* language model use may be subject to access restrictions and user consent. Calling this function
* for the first time (for a extension) will show a consent dialog to the user and because of that this function
* must _only be called in response to a user action!_ Extension can use {@link LanguageModelAccessInformation.canSendRequest}
* to check if they have the necessary permissions to make a request.
*
* - *Note 2:* language models are contributed by other extensions and as they evolve and change,
* the set of available language models may change over time. Therefore it is strongly recommend to check
* {@link languageModels} for available values and handle missing language models gracefully.
*
* This function will return a rejected promise if making a request to the language model is not
* possible. Reasons for this can be:
*
* - user consent not given, see {@link LanguageModelError.NoPermissions `NoPermissions`}
* - model does not exist, see {@link LanguageModelError.NotFound `NotFound`}
* - quota limits exceeded, see {@link LanguageModelError.Blocked `Blocked`}
* - other issues in which case extension must check {@link LanguageModelError.cause `LanguageModelError.cause`}
*
* @param languageModel A language model identifier.
* @param messages An array of message instances.
* @param options Options that control the request.
* @param token A cancellation token which controls the request. See {@link CancellationTokenSource} for how to create one.
* @returns A thenable that resolves to a {@link LanguageModelChatResponse}. The promise will reject when the request couldn't be made.
*/
export function sendChatRequest(languageModel: string, messages: LanguageModelChatMessage[], options?: LanguageModelChatRequestOptions, token?: CancellationToken): Thenable<LanguageModelChatResponse>;
/**
* Uses the language model specific tokenzier and computes the length in token of a given message.
*
* *Note* that this function will throw when the language model does not exist.
*
* @param languageModel A language model identifier.
* @param text A string or a message instance.
* @param token Optional cancellation token.
* @returns A thenable that resolves to the length of the message in tokens.
*/
// TODO@API `undefined` when the language model does not support computing token length
// ollama has nothing
// anthropic suggests to count after the fact https://github.com/anthropics/anthropic-tokenizer-typescript?tab=readme-ov-file#anthropic-typescript-tokenizer
export function computeTokenLength(languageModel: string, text: string | LanguageModelChatMessage, token?: CancellationToken): Thenable<number | undefined>;
// /**
// * Uses the language model specific tokenzier and computes the length in token of a given message.
// *
// * *Note* that this function will throw when the language model does not exist.
// *
// * @param languageModel A language model identifier.
// * @param text A string or a message instance.
// * @param token Optional cancellation token.
// * @returns A thenable that resolves to the length of the message in tokens.
// */
// // TODO@API `undefined` when the language model does not support computing token length
// // ollama has nothing
// // anthropic suggests to count after the fact https://github.com/anthropics/anthropic-tokenizer-typescript?tab=readme-ov-file#anthropic-typescript-tokenizer
// export function computeTokenLength(languageModel: string, text: string | LanguageModelChatMessage, token?: CancellationToken): Thenable<number | undefined>;
}
/**
@ -417,6 +313,7 @@ declare module 'vscode' {
* @return `true` if a request can be made, `false` if not, `undefined` if the language
* model does not exist or consent hasn't been asked for.
*/
// TODO@API applies to chat and embeddings models
canSendRequest(languageModelId: string): boolean | undefined;
}