remix-nlux
Version:
Remix IDE NLUX integration. Remix IDE is the leading IDE for building and deploying smart contracts on Ethereum. NLUX is a JavaScript and React library for building conversational AI experiences.
221 lines (183 loc) • 7.11 kB
text/typescript
import {
ChatAdapterExtras,
ChatItem,
DataTransferMode,
StandardAdapterInfo,
StandardChatAdapter,
StreamingAdapterObserver,
} from '@nlux/core';
import {uid} from '@shared/utils/uid';
import {warn} from '@shared/utils/warn';
import {ChatAdapterOptions} from '../types/adapterOptions';
import {LangServeInputPreProcessor} from '../types/inputPreProcessor';
import {LangServeConfig, LangServeHeaders} from '../types/langServe';
import {LangServeOutputPreProcessor} from '../types/outputPreProcessor';
import {getDataTransferModeToUse} from '../utils/getDataTransferModeToUse';
import {getEndpointUrlToUse} from '../utils/getEndpointUrlToUse';
import {getHeadersToUse} from '../utils/getHeadersToUse';
import {getRunnableNameToUse} from '../utils/getRunnableNameToUse';
import {getSchemaUrlToUse} from '../utils/getSchemaUrlToUse';
import {transformInputBasedOnSchema} from '../utils/transformInputBasedOnSchema';
export abstract class LangServeAbstractAdapter<AiMsg> implements StandardChatAdapter<AiMsg> {
static defaultDataTransferMode: DataTransferMode = 'stream';
private readonly __instanceId: string;
private readonly __options: ChatAdapterOptions<AiMsg>;
private readonly theDataTransferModeToUse: DataTransferMode;
private readonly theEndpointUrlToUse: string;
private readonly theHeadersToUse: LangServeHeaders;
private theInputSchemaToUse: object | undefined;
private readonly theInputSchemaUrlToUse: string;
private readonly theRunnableNameToUse: string;
private readonly theUseInputSchemaOptionToUse: boolean;
protected constructor(options: ChatAdapterOptions<AiMsg>) {
this.__instanceId = `${this.info.id}-${uid()}`;
this.__options = {...options};
this.theDataTransferModeToUse = getDataTransferModeToUse(options);
this.theHeadersToUse = getHeadersToUse(options);
this.theUseInputSchemaOptionToUse = (typeof options.useInputSchema === 'boolean')
? options.useInputSchema
: true;
this.theEndpointUrlToUse = getEndpointUrlToUse(options);
this.theRunnableNameToUse = getRunnableNameToUse(options);
this.theInputSchemaUrlToUse = getSchemaUrlToUse(options, 'input');
this.init();
}
get dataTransferMode(): DataTransferMode {
return this.theDataTransferModeToUse;
}
get endpointUrl(): string {
return this.theEndpointUrlToUse;
}
get headers(): LangServeHeaders {
return this.theHeadersToUse;
}
get id(): string {
return this.__instanceId;
}
get info(): StandardAdapterInfo {
return {
id: 'langserve-adapter',
capabilities: {
chat: true,
fileUpload: false,
textToSpeech: false,
speechToText: false,
},
};
}
get inputPreProcessor(): LangServeInputPreProcessor<AiMsg> | undefined {
return this.__options.inputPreProcessor;
}
get inputSchema(): Readonly<object> | undefined {
return this.theInputSchemaToUse;
}
get outputPreProcessor(): LangServeOutputPreProcessor<AiMsg> | undefined {
return this.__options.outputPreProcessor;
}
get runnableName(): string {
return this.theRunnableNameToUse;
}
get useInputSchema(): boolean {
return this.theUseInputSchemaOptionToUse;
}
protected get config(): LangServeConfig | undefined {
return this.__options.config;
}
private get inputSchemaUrl(): string {
return this.theInputSchemaUrlToUse;
}
abstract batchText(message: string, extras: ChatAdapterExtras<AiMsg>): Promise<string | object | undefined>;
async fetchSchema(url: string): Promise<object | undefined> {
try {
const response = await fetch(url);
const result = await response.json();
if (typeof result !== 'object' || !result) {
warn(`LangServe adapter is unable process schema loaded from: ${url}`);
return undefined;
}
return result;
} catch (_error) {
warn(`LangServe adapter is unable to fetch schema from: ${url}`);
return undefined;
}
}
init() {
if (this.useInputSchema) {
this.fetchSchema(this.inputSchemaUrl).then((schema) => {
this.theInputSchemaToUse = schema;
});
}
}
preProcessAiBatchedMessage(
message: string | object | undefined,
extras: ChatAdapterExtras<AiMsg>,
): AiMsg | undefined {
if (this.outputPreProcessor) {
return this.outputPreProcessor(message);
}
if (typeof message === 'string') {
return message as AiMsg;
}
const content = (message as Record<string, unknown>)?.content;
if (typeof content === 'string') {
return content as AiMsg;
}
warn(
'LangServe adapter is unable to process the response from the runnable. Returning empty string. ' +
'You may want to implement an output pre-processor to handle custom responses.',
);
return undefined;
}
preProcessAiStreamedChunk(
chunk: string | object | undefined,
extras: ChatAdapterExtras<AiMsg>,
): AiMsg | undefined {
if (this.outputPreProcessor) {
return this.outputPreProcessor(chunk);
}
if (typeof chunk === 'string') {
return chunk as AiMsg;
}
const content = (chunk as Record<string, unknown>)?.content;
if (typeof content === 'string') {
return content as AiMsg;
}
warn(
'LangServe adapter is unable to process the chunk from the runnable. Returning empty string. ' +
'You may want to implement an output pre-processor to handle chunks of custom responses.',
);
return undefined;
}
abstract streamText(
message: string,
observer: StreamingAdapterObserver<string | object | undefined>,
extras: ChatAdapterExtras<AiMsg>,
): void;
protected getRequestBody(
message: string,
config?: Record<string, unknown>,
conversationHistory?: ChatItem<AiMsg>[],
): string {
if (this.inputPreProcessor) {
const preProcessedInput = this.inputPreProcessor(message, conversationHistory);
return JSON.stringify({
input: preProcessedInput,
config,
});
}
if (this.inputSchema) {
const body = transformInputBasedOnSchema(message, conversationHistory, this.inputSchema, this.runnableName);
if (typeof body !== 'undefined') {
return JSON.stringify({
input: body,
config,
});
}
}
// By default, we send the message as is
return JSON.stringify({
input: message,
config,
});
}
}