@naktibalda/jorel
Version:
The easiest way to use LLMs, including streams, images, documents, tools and various agent scenarios.
447 lines (446 loc) • 20.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.JorEl = void 0;
const documents_1 = require("../documents");
const providers_1 = require("../providers");
const shared_1 = require("../shared");
const tools_1 = require("../tools");
const jorel_core_1 = require("./jorel.core");
const jorel_team_1 = require("./jorel.team");
/**
* Jor-El: Singular interface for managing multiple LLM providers and models
*/
class JorEl {
/**
* System message use for all requests by default (unless specified per request)
*/
systemMessage;
/**
* Agent related functionality
*/
team;
/**
* Core store for managing providers
* @internal
*/
_core;
/**
* Public methods for managing models
*/
models = {
list: () => this._core.modelManager.listModels(),
register: (params) => {
this._core.providerManager.getProvider(params.provider); // Ensure provider exists
return this._core.modelManager.registerModel(params);
},
unregister: (model) => this._core.modelManager.unregisterModel(model),
getDefault: () => this._core.modelManager.getDefaultModel(),
setDefault: (model) => this._core.modelManager.registerModel({
model,
provider: "",
setAsDefault: true,
}),
embeddings: {
register: (params) => this._core.modelManager.registerEmbeddingModel(params),
unregister: (model) => this._core.modelManager.unregisterEmbeddingModel(model),
getDefault: () => this._core.modelManager.getDefaultEmbeddingModel(),
setDefault: (model) => this._core.modelManager.setDefaultEmbeddingModel(model),
list: () => this._core.modelManager.listEmbeddingModels(),
},
};
/**
* Public methods for managing providers
*/
providers = {
list: () => this._core.providerManager.listProviders(),
registerCustom: (provider, coreProvider) => this._core.providerManager.registerProvider(provider, coreProvider),
registerAnthropic: (config) => {
this._core.providerManager.registerProvider("anthropic", new providers_1.AnthropicProvider(config));
const defaultModels = config?.bedrock ? providers_1.defaultAnthropicBedrockModels : providers_1.defaultAnthropicModels;
for (const model of defaultModels) {
this.models.register({ model, provider: "anthropic" });
}
},
registerGoogleGenAi: (config) => {
this._core.providerManager.registerProvider("google-gen-ai", new providers_1.GoogleGenerativeAIProvider(config));
for (const model of providers_1.defaultGoogleGenAiModels) {
this.models.register({ model, provider: "google-gen-ai" });
}
},
registerGrok: (config) => {
this._core.providerManager.registerProvider("grok", new providers_1.GrokProvider(config));
for (const model of providers_1.defaultGrokModels) {
this.models.register({ model, provider: "grok" });
}
},
registerGroq: (config) => {
this._core.providerManager.registerProvider("groq", new providers_1.GroqProvider(config));
for (const model of providers_1.defaultGroqModels) {
this.models.register({ model, provider: "groq" });
}
},
registerMistral: (config) => {
this._core.providerManager.registerProvider("mistral", new providers_1.MistralProvider(config));
for (const model of providers_1.defaultMistralAiModels) {
this.models.register({ model, provider: "mistral" });
}
for (const { model, dimensions } of providers_1.defaultMistralAiEmbeddingModels) {
this.models.embeddings.register({ model, dimensions, provider: "mistral" });
}
},
registerOllama: (config) => {
this._core.providerManager.registerProvider("ollama", new providers_1.OllamaProvider(config));
},
registerOpenAi: (config) => {
this._core.providerManager.registerProvider("openai", new providers_1.OpenAIProvider(config));
for (const model of providers_1.defaultOpenAiModels) {
this.models.register({ model, provider: "openai" });
}
for (const { model, dimensions } of providers_1.defaultOpenAiEmbeddingModels) {
this.models.embeddings.register({ model, dimensions, provider: "openai" });
}
},
registerOpenRouter: (config) => {
this._core.providerManager.registerProvider("open-router", new providers_1.OpenRouterProvider(config));
for (const model of providers_1.defaultOpenRouterModels) {
this.models.register({ model, provider: "open-router" });
}
},
registerGoogleVertexAi: (config) => {
this._core.providerManager.registerProvider("google-vertex-ai", new providers_1.GoogleVertexAiProvider(config));
for (const model of providers_1.defaultVertexAiModels) {
this.models.register({ model, provider: "google-vertex-ai" });
}
},
anthropic: {
addModel: (model) => this.models.register({ model, provider: "anthropic" }),
getClient: () => this._core.providerManager.getProvider("anthropic").client,
},
googleGenAi: {
addModel: (model) => this.models.register({ model, provider: "google-gen-ai" }),
getClient: () => this._core.providerManager.getProvider("google-gen-ai").client,
},
grok: {
addModel: (model) => this.models.register({ model, provider: "grok" }),
getClient: () => this._core.providerManager.getProvider("grok").client,
},
groq: {
addModel: (model) => this.models.register({ model, provider: "groq" }),
getClient: () => this._core.providerManager.getProvider("groq").client,
},
mistral: {
addModel: (model) => this.models.register({ model, provider: "mistral" }),
getClient: () => this._core.providerManager.getProvider("mistral").client,
},
openAi: {
addModel: (model) => this.models.register({ model, provider: "openai" }),
getClient: () => this._core.providerManager.getProvider("openai").client,
},
openRouter: {
addModel: (model) => this.models.register({ model, provider: "open-router" }),
getClient: () => this._core.providerManager.getProvider("open-router").client,
},
vertexAi: {
addModel: (model) => this.models.register({ model, provider: "google-vertex-ai" }),
getClient: () => this._core.providerManager.getProvider("google-vertex-ai").client,
},
};
/**
* Create a new Jor-El instance.
*
* @param config - The configuration for the Jor-El instance.
* @param config.anthropic - Anthropic configuration (optional).
* @param config.googleGenAi - Google Generative AI configuration (optional).
* @param config.grok - Grok configuration (optional).
* @param config.groq - Groq configuration (optional).
* @param config.vertexAi - Google Vertex AI configuration (optional).
* @param config.ollama - Ollama configuration (optional).
* @param config.openAI - OpenAI configuration (optional).
* @param config.openRouter - OpenRouter configuration (optional).
* @param config.systemMessage - System message to include in all requests (optional).
* @param config.documentSystemMessage - System message to include in all requests with documents (optional).
* @param config.temperature - Default temperature for all requests (optional).
*/
constructor(config = {}) {
this.systemMessage = config.systemMessage ?? "You are a helpful assistant.";
this._documentSystemMessage = config.documentSystemMessage
? this.validateDocumentSystemMessage(config.documentSystemMessage)
: "Here are some documents that you can consider in your response: {{documents}}";
this._core = new jorel_core_1.JorElCoreStore({
temperature: config.temperature === undefined ? 0 : config.temperature,
logger: config.logger,
logLevel: config.logLevel,
});
this.team = new jorel_team_1.JorElAgentManager(this._core);
if (config.anthropic)
this.providers.registerAnthropic(config.anthropic === true ? undefined : config.anthropic);
if (config.googleGenAi)
this.providers.registerGoogleGenAi(config.googleGenAi === true ? undefined : config.googleGenAi);
if (config.grok)
this.providers.registerGrok(config.grok === true ? undefined : config.grok);
if (config.groq)
this.providers.registerGroq(config.groq === true ? undefined : config.groq);
if (config.mistral)
this.providers.registerMistral(config.mistral === true ? undefined : config.mistral);
if (config.vertexAi)
this.providers.registerGoogleVertexAi(config.vertexAi === true ? undefined : config.vertexAi);
if (config.ollama)
this.providers.registerOllama(config.ollama === true ? undefined : config.ollama);
if (config.openAI)
this.providers.registerOpenAi(config.openAI === true ? undefined : config.openAI);
if (config.openRouter)
this.providers.registerOpenRouter(config.openRouter === true ? undefined : config.openRouter);
}
/** @internal */
_documentSystemMessage;
/**
* Default document system message for all requests (only used when documents are included)
*/
get documentSystemMessage() {
return this._documentSystemMessage;
}
/**
* Set the default document system message for all requests (only used when documents are included)
*/
set documentSystemMessage(documentSystemMessage) {
this._documentSystemMessage = this.validateDocumentSystemMessage(documentSystemMessage);
}
/**
* Default temperature for all requests
*/
get temperature() {
return this._core.defaultConfig.temperature;
}
/**
* Set the default temperature for all requests
*/
set temperature(temperature) {
this._core.defaultConfig.temperature = temperature;
}
/**
* Logger instance
*/
get logger() {
return this._core.logger;
}
/**
* Set the logger instance
*/
set logger(logger) {
this._core.logger = logger;
}
/**
* Log level
*/
get logLevel() {
return this._core.logger.logLevel;
}
/**
* Set the log level
*/
set logLevel(logLevel) {
this._core.logger.logLevel = logLevel;
}
/**
* Generate a response for a given set of messages.
*
* @param messages - The messages to generate a response for.
* @param config - The configuration for the generation.
* @param config.model - Model to use for this generation (optional).
* @param config.systemMessage - System message to include in this request (optional).
* @param config.temperature - Temperature for this request (optional).
* @param config.tools - Tools to use for this request (optional).
*/
async generate(messages, config = {}) {
return this._core.generate(messages, config);
}
async ask(task, config = {}, includeMeta = false) {
// @ts-expect-error ts(2769) - overloads
return this.text(task, config, includeMeta);
}
async text(task, config = {}, includeMeta = false) {
const { systemMessage, documents, documentSystemMessage, messageHistory, ...coreConfig } = config;
const _messages = await this.generateMessages(task, systemMessage, documents, documentSystemMessage, messageHistory);
const _config = {
...coreConfig,
tools: config.tools
? config.tools instanceof tools_1.LlmToolKit
? config.tools
: new tools_1.LlmToolKit(config.tools)
: undefined,
};
const { output, messages } = await this._core.generateAndProcessTools(_messages, _config);
const response = output.content || "";
const meta = output.meta;
return includeMeta ? { response, meta, messages } : response;
}
async json(task, config = {}, includeMeta = false) {
const { systemMessage, documents, documentSystemMessage, messageHistory, ...coreConfig } = config;
const _messages = await this.generateMessages(task, systemMessage, documents, documentSystemMessage, messageHistory);
const _config = {
...coreConfig,
json: config.jsonSchema || true,
tools: config.tools
? config.tools instanceof tools_1.LlmToolKit
? config.tools
: new tools_1.LlmToolKit(config.tools)
: undefined,
};
const { output, messages } = await this._core.generateAndProcessTools(_messages, _config);
const parsed = output.content ? tools_1.LlmToolKit.deserialize(output.content) : {};
return includeMeta ? { response: parsed, meta: output.meta, messages } : parsed;
}
/**
* Generate a stream of response chunks for a given set of messages.
*
* @param messages - The messages to generate a response for.
* @param config - The configuration for the generation.
*/
async *generateContentStream(messages, config = {}) {
yield* this._core.generateContentStream(messages, config);
}
/**
* Generate a stream of response chunks for a given task.
*
* @param task - The task to generate a response for (either a string or an array of strings and ImageContent objects).
* @param config - Configuration for the specific generation.
*/
async *stream(task, config = {}) {
const stream = this.streamWithMeta(task, config);
for await (const chunk of stream) {
if (chunk.type === "chunk" && chunk.content)
yield chunk.content;
}
}
/**
* Generate a stream of response chunks for a given task with metadata.
*
* @param task - The task to generate a response for (either a string or an array of strings and ImageContent objects).
* @param config - Configuration for the specific generation.
*/
async *streamWithMeta(task, config = {}) {
const { systemMessage, documents, documentSystemMessage, messageHistory, ...coreConfig } = config;
const messages = await this.generateMessages(task, systemMessage, documents, documentSystemMessage, messageHistory);
const _config = {
...coreConfig,
tools: config.tools
? config.tools instanceof tools_1.LlmToolKit
? config.tools
: new tools_1.LlmToolKit(config.tools)
: undefined,
};
if (config.tools) {
yield* this._core.generateStreamAndProcessTools(messages, _config);
}
else {
const stream = this._core.generateContentStream(messages, _config);
for await (const chunk of stream) {
if (chunk.type === "chunk") {
yield chunk;
}
if (chunk.type === "response") {
yield chunk;
if (chunk.role === "assistant") {
messages.push({
id: (0, shared_1.generateUniqueId)(),
role: "assistant",
content: chunk.content,
createdAt: Date.now(),
});
}
else {
messages.push({
id: (0, shared_1.generateUniqueId)(),
role: "assistant_with_tools",
content: chunk.content,
toolCalls: chunk.toolCalls,
createdAt: Date.now(),
});
}
yield { type: "messages", messages };
}
}
}
}
/**
* Create an embedding for a given text.
*
* @param text - The text to create an embedding for.
* @param config - The configuration for the embedding.
* @param config.model - The model to use for the embedding (optional).
*/
async embed(text, config = {}) {
return this._core.generateEmbedding(text, config.model);
}
/**
* Generate a system message - optionally with a set of documents.
*
* @param systemMessage - The system message to use.
* @param documents - The documents to include in the system message (optional).
* @param documentSystemMessage - The system message to use for documents (optional).
*/
generateSystemMessage(systemMessage = "", { documents, documentSystemMessage, } = {}) {
const _documents = documents instanceof documents_1.LlmDocumentCollection ? documents : new documents_1.LlmDocumentCollection(documents);
return (0, providers_1.generateSystemMessage)(systemMessage || this.systemMessage, documentSystemMessage || this._documentSystemMessage, _documents);
}
/**
* Generate a user message.
*
* @param content - The content to include in the user message.
*/
generateUserMessage(content) {
return (0, providers_1.generateUserMessage)(content);
}
/**
* Helper to generate messages for a given task input.
*
* @param content - The task input content (either a string or an array of strings and ImageContent objects).
* @param systemMessage - The system message to include (optional).
* @param documents - The documents to include in the system message (optional).
* @param documentSystemMessage - The system message to use for documents (optional).
* @param messageHistory - The message history to include (optional). If provided along with a dedicated system
* message, the system message inside the messages will be ignored.
* @internal
*/
async generateMessages(content, systemMessage, documents, documentSystemMessage, messageHistory = []) {
if (Array.isArray(content)) {
if (content.length === 0) {
throw new Error("The task input must not be an empty array.");
}
}
else {
if (!content) {
throw new Error("The task input must not be empty.");
}
}
const _userMessage = await this.generateUserMessage(content);
// Empty string overrides default to skip system message
if (systemMessage !== "" && (systemMessage || this.systemMessage)) {
if (messageHistory && messageHistory.some((m) => m.role === "system")) {
this._core.logger.info("JorEl", "Message history contains system messages. These will be ignored.");
}
const _systemMessage = this.generateSystemMessage(systemMessage, { documents, documentSystemMessage });
return [_systemMessage, ...messageHistory.filter((m) => m.role !== "system"), _userMessage];
}
else {
if (documents && documents.length > 0) {
this.logger.warn("JorEl", "Documents were provided but no system message was included. The documents will not be included in the response.");
}
}
return [...messageHistory, _userMessage];
}
/**
* Helper to validate the document system message.
*
* @param documentSystemMessage - The document system message to validate.
* @internal
*/
validateDocumentSystemMessage(documentSystemMessage) {
if (!documentSystemMessage)
return documentSystemMessage;
if (documentSystemMessage.includes("{{documents}}"))
return documentSystemMessage;
throw new Error('The "documentSystemMessage" must either be empty or include the placeholder "{{documents}}" to insert the document list.');
}
}
exports.JorEl = JorEl;