UNPKG

@daitanjs/intelligence

Version:

A modular library for advanced LLM orchestration, stateful RAG, multi-step agentic workflows, and tool use, built on LangChain.js.

1,419 lines (1,399 loc) 277 kB
// src/intelligence/src/index.js import { getLogger as getLogger49 } from "@daitanjs/development"; // src/intelligence/src/orchestration/daitanOrchestrator.js import { getLogger as getLogger46 } from "@daitanjs/development"; import { getConfigManager as getConfigManager18 } from "@daitanjs/config"; // src/intelligence/src/intelligence/core/llmOrchestrator.js import { ChatOpenAI } from "@langchain/openai"; import { ChatAnthropic } from "@langchain/anthropic"; import { ChatGroq } from "@langchain/groq"; import { StringOutputParser, JsonOutputParser } from "@langchain/core/output_parsers"; import { getConfigManager } from "@daitanjs/config"; import { DaitanApiError, DaitanConfigurationError } from "@daitanjs/error"; // src/intelligence/src/intelligence/core/promptBuilder.js import { HumanMessage, SystemMessage, AIMessage, BaseMessage } from "@langchain/core/messages"; import { getLogger } from "@daitanjs/development"; var promptBuilderLogger = getLogger("daitan-prompt-builder"); function convertToLangChainMessages(messages) { if (!Array.isArray(messages)) { return []; } return messages.reduce((acc, msg) => { if (msg instanceof BaseMessage) { acc.push(msg); } else if (typeof msg === "object" && msg !== null && typeof msg.role === "string" && (typeof msg.content === "string" || Array.isArray(msg.content) || typeof msg.content === "object")) { const role = msg.role.toLowerCase(); try { if (role === "system") acc.push(new SystemMessage({ content: msg.content })); else if (role === "user" || role === "human") acc.push(new HumanMessage({ content: msg.content })); else if (role === "assistant" || role === "ai") acc.push(new AIMessage({ content: msg.content })); else { promptBuilderLogger.warn( `Unknown message role "${msg.role}". Treating as human.` ); acc.push(new HumanMessage({ content: msg.content })); } } catch (e) { promptBuilderLogger.error( "Failed to create LangChain message from object.", { messageObject: msg, error: e.message } ); } } return acc; }, []); } var buildLlmMessages = (prompt = {}) => { const systemConfig = prompt.system || {}; const userContent = prompt.user; const fewShotExamples = prompt.shots || []; const systemInstructionParts = [ systemConfig.persona, systemConfig.whoYouAre, // Legacy support systemConfig.task, systemConfig.whatYouDo, // Legacy support systemConfig.guidelines, systemConfig.vitals, systemConfig.scoring, systemConfig.writingStyle, systemConfig.outputFormat, systemConfig.outputFormatDescription, // Legacy support systemConfig.promptingTips, systemConfig.reiteration ]; const systemInstructionContent = systemInstructionParts.filter(Boolean).join("\n\n"); let messages = []; if (systemInstructionContent) { messages.push({ role: "system", content: systemInstructionContent }); } if (Array.isArray(fewShotExamples) && fewShotExamples.length > 0) { messages.push( ...fewShotExamples.filter( (s) => s && typeof s.role === "string" && (typeof s.content === "string" || Array.isArray(s.content) || typeof s.content === "object") ) ); } if (userContent && (typeof userContent === "string" || typeof userContent === "object")) { messages.push({ role: "user", content: userContent }); } return convertToLangChainMessages(messages); }; // src/intelligence/src/intelligence/core/expertModels.js import { getEnvVariable } from "@daitanjs/development"; var DEFAULT_EXPERT_PROFILE_NAME = getEnvVariable( "DEFAULT_EXPERT_PROFILE", "FAST_TASKER" ); var getExpertConfig = (envVarKey, defaultValue) => { const combinedValue = getEnvVariable(envVarKey, defaultValue); const parts = combinedValue.split("|"); const provider = parts[0]?.trim(); const model = parts[1]?.trim(); if (!provider || !model) { const [defaultProvider, defaultModel] = defaultValue.split("|"); return { provider: defaultProvider.trim(), model: defaultModel.trim() }; } return { provider, model }; }; var EXPERT_MODELS = { MASTER_COMMUNICATOR: { ...getExpertConfig("LLM_EXPERT_MASTER_COMMUNICATOR", "openai|gpt-4o-mini"), description: "Expert in clear, concise, and engaging communication.", temperature: 0.7 }, CREATIVE_WRITER: { ...getExpertConfig("LLM_EXPERT_CREATIVE_WRITER", "openai|gpt-4-turbo"), description: "Expert in creative writing, storytelling, and brainstorming.", temperature: 0.9 }, FAST_TASKER: { ...getExpertConfig("LLM_EXPERT_FAST_TASKER", "openai|gpt-4o-mini"), description: "Optimized for speed on less complex tasks.", temperature: 0.5 }, LOCAL_DEFAULT: { ...getExpertConfig("LLM_EXPERT_LOCAL_DEFAULT", "ollama|llama3:instruct"), description: "A general-purpose model running locally via Ollama.", temperature: 0.7 }, MASTER_CODER: { ...getExpertConfig( "LLM_EXPERT_MASTER_CODER", "anthropic|claude-3-opus-20240229" ), description: "Expert in code generation, debugging, and explanation.", temperature: 0.3 }, CODING_STUDENT: { ...getExpertConfig("LLM_EXPERT_CODING_STUDENT", "openai|gpt-4o-mini"), description: "Capable, cost-effective coding assistant for simpler tasks.", temperature: 0.5 }, SENTIMENT_WIZARD: { ...getExpertConfig("LLM_EXPERT_SENTIMENT_WIZARD", "openai|gpt-3.5-turbo"), description: "Specialized in sentiment analysis and understanding nuanced text.", temperature: 0.2 }, TRANSLATION_MULTILINGUAL: { ...getExpertConfig( "LLM_EXPERT_TRANSLATION_MULTILINGUAL", "openai|gpt-4o-mini" ), description: "Expert in multilingual translation.", temperature: 0.1 }, DATA_ANALYSIS_EXPERT: { ...getExpertConfig("LLM_EXPERT_DATA_ANALYSIS", "openai|gpt-4-turbo"), description: "Expert in interpreting data and generating insights.", temperature: 0.4 }, RESEARCH_ASSISTANT: { ...getExpertConfig("LLM_EXPERT_RESEARCH_ASSISTANT", "openai|gpt-4-turbo"), description: "Specialized in synthesizing information and research queries.", temperature: 0.5 } }; var getExpertModelDefinition = (expertName) => { if (typeof expertName !== "string" || !expertName.trim()) return void 0; return EXPERT_MODELS[expertName.toUpperCase()]; }; // src/intelligence/src/intelligence/core/llmPricing.js import { getLogger as getLogger2 } from "@daitanjs/development"; var logger = getLogger2("llm-pricing"); var PROVIDER_MODEL_PRICING = { openai: { "gpt-4o": { inputCostPer1MTokens: 5, outputCostPer1MTokens: 15 }, "gpt-4o-mini": { inputCostPer1MTokens: 0.15, outputCostPer1MTokens: 0.6 }, "gpt-4-turbo": { inputCostPer1MTokens: 10, outputCostPer1MTokens: 30 }, "gpt-4": { inputCostPer1MTokens: 30, outputCostPer1MTokens: 60 }, "gpt-3.5-turbo": { inputCostPer1MTokens: 0.5, outputCostPer1MTokens: 1.5 }, "text-embedding-3-large": { inputCostPer1MTokens: 0.13, outputCostPer1MTokens: 0 }, "text-embedding-3-small": { inputCostPer1MTokens: 0.02, outputCostPer1MTokens: 0 }, "text-embedding-ada-002": { inputCostPer1MTokens: 0.1, outputCostPer1MTokens: 0 } }, anthropic: { "claude-3-opus-20240229": { inputCostPer1MTokens: 15, outputCostPer1MTokens: 75 }, "claude-3-sonnet-20240229": { inputCostPer1MTokens: 3, outputCostPer1MTokens: 15 }, "claude-3-haiku-20240307": { inputCostPer1MTokens: 0.25, outputCostPer1MTokens: 1.25 } }, groq: { "llama3-8b-8192": { inputCostPer1MTokens: 0.05, outputCostPer1MTokens: 0.1 }, "llama3-70b-8192": { inputCostPer1MTokens: 0.59, outputCostPer1MTokens: 0.79 }, "mixtral-8x7b-32768": { inputCostPer1MTokens: 0.27, outputCostPer1MTokens: 0.27 } }, ollama: { "llama3:instruct": { inputCostPer1MTokens: 0, outputCostPer1MTokens: 0, details: "Local model, no cost." }, "nomic-embed-text": { inputCostPer1MTokens: 0, outputCostPer1MTokens: 0, details: "Local model, no cost." } } }; var estimateLlmCost = (provider, model, inputTokens = 0, outputTokens = 0) => { const providerKey = provider?.toLowerCase(); const modelKey = model?.toLowerCase(); const result = { estimatedCostUSD: null, currency: "USD", details: "No pricing information available." }; if (!providerKey || !modelKey) { logger.debug("Provider or model key missing for cost estimation."); return result; } const providerPricing = PROVIDER_MODEL_PRICING[providerKey]; if (!providerPricing) { result.details = `No pricing for provider '${providerKey}'.`; return result; } const baseModelKey = Object.keys(providerPricing).find( (key) => modelKey.startsWith(key) ); if (!baseModelKey) { result.details = `No pricing for model '${modelKey}' under provider '${providerKey}'.`; return result; } const modelPricing = providerPricing[baseModelKey]; if (modelPricing.inputCostPer1MTokens === 0 && modelPricing.outputCostPer1MTokens === 0) { result.estimatedCostUSD = 0; result.details = modelPricing.details || "Model is free or local."; return result; } const inputCost = inputTokens / 1e6 * (modelPricing.inputCostPer1MTokens || 0); const outputCost = outputTokens / 1e6 * (modelPricing.outputCostPer1MTokens || 0); result.estimatedCostUSD = inputCost + outputCost; result.details = `Cost calculated for ${providerKey}/${baseModelKey}.`; return result; }; // src/intelligence/src/intelligence/core/tokenUtils.js import { get_encoding } from "tiktoken"; import { getLogger as getLogger3 } from "@daitanjs/development"; var logger2 = getLogger3("token-utils"); var DEFAULT_CHAR_TO_TOKEN_RATIO = 4; var tiktokenCache = /* @__PURE__ */ new Map(); var getTiktokenForModel = (modelNameInput) => { const modelName = String(modelNameInput || ""); if (tiktokenCache.has(modelName)) { return tiktokenCache.get(modelName); } let encodingName; if (modelName.startsWith("gpt-4") || modelName.startsWith("gpt-3.5-turbo") || modelName.startsWith("text-embedding-3") || modelName.startsWith("gpt-4o")) { encodingName = "cl100k_base"; } else if (modelName.includes("text-embedding-ada-002")) { encodingName = "p50k_base"; } else { logger2.debug( `No specific tiktoken encoding for model "${modelName}". Defaulting to cl100k_base.` ); encodingName = "cl100k_base"; } try { const encoding = get_encoding(encodingName); tiktokenCache.set(modelName, encoding); return encoding; } catch (error) { logger2.warn( `Could not initialize tiktoken for model "${modelName}" (encoding: ${encodingName}): ${error.message}.` ); return null; } }; var countTokens = (text, modelName, providerName = "openai") => { if (typeof text !== "string" || text === "") return 0; const openaiCompatibleProviders = [ "openai", "groq", "openrouter", "anthropic" ]; if (openaiCompatibleProviders.includes(providerName?.toLowerCase() || "")) { const tiktokenInstance = getTiktokenForModel(modelName); if (tiktokenInstance) { try { return tiktokenInstance.encode(text).length; } catch (error) { logger2.warn( `Tiktoken encoding failed for model "${modelName}". Falling back to char count.` ); } } } const estimatedTokens = Math.ceil(text.length / DEFAULT_CHAR_TO_TOKEN_RATIO); logger2.debug( `Using char-based token approximation for model "${modelName}" (provider: ${providerName}). Chars: ${text.length}, Approx Tokens: ${estimatedTokens}` ); return estimatedTokens; }; var countTokensForMessages = (messages, modelName, providerName = "openai") => { if (!Array.isArray(messages) || messages.length === 0) return 0; const tiktokenInstance = getTiktokenForModel(modelName); if (!tiktokenInstance) { let totalChars = 0; messages.forEach((msg) => { if (typeof msg.content === "string") { totalChars += msg.content.length; } }); return Math.ceil(totalChars / DEFAULT_CHAR_TO_TOKEN_RATIO) + messages.length * 2; } let tokensPerMessage = 3; let tokensPerName = 1; let numTokens = 0; messages.forEach((message) => { numTokens += tokensPerMessage; for (const key in message) { if (key === "content" && typeof message.content === "string") { numTokens += tiktokenInstance.encode(message.content).length; } else if (key === "name" && message[key]) { numTokens += tokensPerName; } } }); numTokens += 3; return numTokens; }; // src/intelligence/src/intelligence/core/llmOrchestrator.js import { getLogger as getLogger4 } from "@daitanjs/development"; var logger3 = getLogger4("daitan-llm-orchestrator"); function extractJsonFromString(text) { if (typeof text !== "string") return text; const jsonRegex = /```(?:json)?\s*([\s\S]+?)\s*```|({[\s\S]*}|\[[\s\S]*\])/m; const match = text.match(jsonRegex); return match ? match[1] || match[2] || text : text; } var generateIntelligence = async ({ prompt = {}, config = {}, callbacks, metadata = {} // Add metadata to destructuring }) => { const { llm: llmConfig = {}, response: responseConfig = {}, ...otherConfigs } = config; if (!prompt.user && (!prompt.shots || prompt.shots.length === 0)) { throw new DaitanConfigurationError( "A `prompt.user` message or messages in `prompt.shots` are required." ); } let llm; let providerName; let modelName; let temperature; try { const configManager = getConfigManager(); const rawTarget = llmConfig.target || configManager.get("DEFAULT_EXPERT_PROFILE") || configManager.get("LLM_PROVIDER") || "FAST_TASKER"; const expertDef = getExpertModelDefinition(rawTarget); if (expertDef) { providerName = expertDef.provider.toLowerCase(); modelName = expertDef.model; temperature = expertDef.temperature; } else { const [p, m] = rawTarget.split("|"); providerName = p ? p.toLowerCase() : "openai"; modelName = m; } const commonConfig = { temperature: temperature ?? llmConfig.temperature ?? 0.7, maxRetries: config.retry?.maxAttempts ?? 2, timeout: llmConfig.requestTimeout, modelName }; switch (providerName) { case "openai": { const apiKey = llmConfig.apiKey || configManager.get("OPENAI_API_KEY"); if (!apiKey) throw new DaitanConfigurationError("OPENAI_API_KEY not found."); llm = new ChatOpenAI({ ...commonConfig, apiKey }); break; } case "anthropic": { const apiKey = llmConfig.apiKey || configManager.get("ANTHROPIC_API_KEY"); if (!apiKey) throw new DaitanConfigurationError("ANTHROPIC_API_KEY not found."); llm = new ChatAnthropic({ ...commonConfig, apiKey }); break; } case "groq": { const apiKey = llmConfig.apiKey || configManager.get("GROQ_API_KEY"); if (!apiKey) throw new DaitanConfigurationError("GROQ_API_KEY not found."); llm = new ChatGroq({ ...commonConfig, apiKey }); break; } default: throw new DaitanConfigurationError( `Unsupported provider in orchestrator: '${providerName}'` ); } const messages = buildLlmMessages(prompt); const result = await llm.invoke(messages, { callbacks }); const rawContent = result.content ?? ""; let contentToParse = rawContent; if (responseConfig.format === "json") { contentToParse = extractJsonFromString(rawContent); } const parser = responseConfig.format === "json" ? new JsonOutputParser() : new StringOutputParser(); const finalResponse = await parser.parse(contentToParse); const usageMetadata = result.usage_metadata ?? {}; const usage = { inputTokens: usageMetadata.inputTokens ?? await countTokensForMessages(messages, modelName, providerName), outputTokens: usageMetadata.outputTokens ?? await countTokens( typeof finalResponse === "string" ? finalResponse : JSON.stringify(finalResponse ?? ""), modelName, providerName ) }; usage.totalTokens = (usage.inputTokens || 0) + (usage.outputTokens || 0); const cost = estimateLlmCost( providerName, modelName, usage.inputTokens, usage.outputTokens ); return { response: finalResponse, usage: { ...usage, ...cost }, rawResponse: rawContent }; } catch (error) { logger3.error( `LLM Interaction Failed. Summary: ${metadata.summary || "N/A"}. Provider: ${providerName}, Model: ${modelName}. Error: ${error.message}`, { errorStack: error.stack } ); throw new DaitanApiError( `An unrecoverable error occurred during the LLM interaction with provider '${providerName || "unknown"}'.`, providerName || "unknown", error?.status, { model: modelName, summary: metadata.summary }, error ); } }; // src/intelligence/src/services/llmService.js import { getLogger as getLogger5 } from "@daitanjs/development"; import { getConfigManager as getConfigManager2 } from "@daitanjs/config"; import { DaitanInvalidInputError } from "@daitanjs/error"; var logger4 = getLogger5("daitan-llm-service"); var LLMService = class { /** * @param {LLMServiceConfig} [defaultConfig={}] - Default configuration for this service instance. */ constructor(defaultConfig = {}) { const configManager = getConfigManager2(); this.defaultConfig = { target: configManager.get("LLM_PROVIDER", "openai"), // Default to provider if no specific target temperature: 0.7, maxTokens: 2e3, verbose: configManager.get("DEBUG_INTELLIGENCE", false), trackUsage: configManager.get("LLM_TRACK_USAGE", true), ...defaultConfig }; this.logger = logger4; logger4.info("LLMService initialized."); logger4.debug("LLMService default configuration:", this.defaultConfig); } /** * Makes a generic call to `generateIntelligence`, merging service defaults with call-specific options. * @param {GenerateIntelligenceParams} options - Options for generateIntelligence, including prompt, config, metadata, and callbacks. * @returns {Promise<import('../intelligence/core/llmOrchestrator.js').GenerateIntelligenceResult<any>>} */ async generate(options) { const { prompt = {}, config: callConfig = {}, metadata = {}, callbacks } = options; if (!prompt?.user && !(prompt?.shots && prompt.shots.length > 0)) { throw new DaitanInvalidInputError( "LLMService.generate: A `prompt.user` message or messages in `prompt.shots` are required." ); } const { llm: callLlm = {}, response: callResponse = {}, retry: callRetry = {}, ...callRootConfig } = callConfig; const finalConfig = { verbose: this.defaultConfig.verbose, trackUsage: this.defaultConfig.trackUsage, ...callRootConfig, llm: { target: this.defaultConfig.target, temperature: this.defaultConfig.temperature, maxTokens: this.defaultConfig.maxTokens, apiKey: this.defaultConfig.apiKey, baseURL: this.defaultConfig.baseURL, ...callLlm }, response: { ...callResponse }, retry: { maxAttempts: this.defaultConfig.maxRetries, ...callRetry } }; const finalParams = { prompt, config: finalConfig, metadata, callbacks }; const summary = metadata?.summary || "Untitled LLMService Call"; this.logger.info(`LLMService.generate called for summary: "${summary}"`); try { const result = await generateIntelligence(finalParams); this.logger.info(`LLMService.generate successful for: "${summary}"`); if (result.usage) { this.logger.debug("LLM Usage:", result.usage); } return result; } catch (error) { this.logger.error( `LLMService.generate failed for "${summary}": ${error.message}`, { error } ); throw error; } } /** * Generates a JSON response from the LLM. * @param {Object} params * @returns {Promise<{response: Object, usage: LLMUsageInfo | null}>} */ async generateJson({ userPrompt, whoYouAre, whatYouDo, summary = "JSON Generation", shots, ...overrideConfig }) { const { target, temperature, maxTokens, apiKey, baseURL, ...rootConfig } = overrideConfig; const params = { prompt: { system: { persona: whoYouAre, task: whatYouDo }, user: userPrompt, shots }, config: { ...rootConfig, response: { format: "json" }, llm: { target, temperature, maxTokens, apiKey, baseURL } }, metadata: { summary } }; return this.generate(params); } /** * Generates a text response from the LLM. * @param {Object} params * @returns {Promise<{response: string, usage: LLMUsageInfo | null}>} */ async generateText({ userPrompt, whoYouAre, whatYouDo, summary = "Text Generation", shots, ...overrideConfig }) { const { target, temperature, maxTokens, apiKey, baseURL, ...rootConfig } = overrideConfig; const params = { prompt: { system: { persona: whoYouAre, task: whatYouDo }, user: userPrompt, shots }, config: { ...rootConfig, response: { format: "text" }, llm: { target, temperature, maxTokens, apiKey, baseURL } }, metadata: { summary } }; return this.generate(params); } /** * Streams a text response from the LLM. * @param {Object} params * @returns {Promise<{response: string | undefined, usage: LLMUsageInfo | null}>} */ async streamText({ userPrompt, whoYouAre, whatYouDo, summary = "Text Streaming", shots, callbacks, returnFullResponseAfterStream = true, ...overrideConfig }) { if (!callbacks || typeof callbacks.onTokenStream !== "function") { throw new DaitanInvalidInputError( "LLMService.streamText: `callbacks.onTokenStream` is required for streaming." ); } const { target, temperature, maxTokens, apiKey, baseURL, ...rootConfig } = overrideConfig; const params = { prompt: { system: { persona: whoYouAre, task: whatYouDo }, user: userPrompt, shots }, config: { ...rootConfig, response: { format: "text", returnFullResponseAfterStream }, llm: { target, temperature, maxTokens, apiKey, baseURL } }, metadata: { summary }, callbacks }; return this.generate(params); } }; // src/intelligence/src/intelligence/index.js import { getLogger as getLogger45 } from "@daitanjs/development"; // src/intelligence/src/intelligence/core/embeddingGenerator.js import { getLogger as getLogger6 } from "@daitanjs/development"; import { generateEmbedding, generateBatchEmbeddings } from "@daitanjs/embeddings"; var embeddingGeneratorLogger = getLogger6("daitan-embedding-generator"); embeddingGeneratorLogger.info( "Embedding generator module is a re-export layer. All embedding functionalities are canonical in @daitanjs/embeddings." ); // src/intelligence/src/intelligence/core/toolFactory.js import { DynamicTool } from "@langchain/core/tools"; import { getLogger as getLogger7 } from "@daitanjs/development"; import { DaitanInvalidInputError as DaitanInvalidInputError2, DaitanValidationError as DaitanValidationError2, DaitanOperationError } from "@daitanjs/error"; import { ZodError } from "zod"; var toolFactoryLogger = getLogger7("daitan-tool-factory"); var createDaitanTool = (name, description, func, argsSchema = void 0) => { if (!name || typeof name !== "string" || !name.trim()) { throw new DaitanInvalidInputError2( "Tool `name` must be a non-empty string." ); } if (!description || typeof description !== "string" || !description.trim()) { throw new DaitanInvalidInputError2( "Tool `description` must be a non-empty string." ); } if (typeof func !== "function") { throw new DaitanInvalidInputError2( "Tool `func` must be a callable function." ); } const daitanToolWrapperFunc = async (rawInput) => { const callId = `tool-run-${name}-${Date.now().toString(36)}`; const logger13 = getLogger7(`daitan-tool-${name}`); logger13.info(`Tool "${name}" execution: START`, { callId, rawInput }); let inputForRun = rawInput; try { if (typeof rawInput === "string") { try { inputForRun = JSON.parse(rawInput); } catch (e) { } } if (argsSchema) { inputForRun = argsSchema.parse(inputForRun); } const result = await func(inputForRun, callId); const outputString = typeof result === "string" ? result : JSON.stringify(result, null, 2); logger13.info(`Tool "${name}" execution: SUCCESS`, { callId, outputPreview: outputString.substring(0, 150) + "..." }); return outputString; } catch (error) { logger13.error(`Execution error in tool "${name}": ${error.message}`, { callId, errorName: error.name }); if (error instanceof ZodError) { const validationErrorMessage = "Invalid input. " + error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; "); return `Error: ${validationErrorMessage}`; } if (error instanceof DaitanValidationError2 || error instanceof DaitanOperationError) { return `Error: ${error.message}`; } return `Error executing tool "${name}": An unexpected error occurred. ${error.message}`; } }; const toolConfig = { name: name.trim(), description: description.trim(), func: daitanToolWrapperFunc, schema: argsSchema }; return new DynamicTool(toolConfig); }; // src/intelligence/src/intelligence/rag/index.js import { getLogger as getLogger20 } from "@daitanjs/development"; // src/intelligence/src/intelligence/rag/retrieval.js import path from "path"; // src/intelligence/src/intelligence/rag/vectorStoreFactory.js import { getLogger as getLogger11 } from "@daitanjs/development"; import { getConfigManager as getConfigManager6 } from "@daitanjs/config"; import { DaitanConfigurationError as DaitanConfigurationError4, DaitanOperationError as DaitanOperationError5, DaitanInvalidInputError as DaitanInvalidInputError4 } from "@daitanjs/error"; // src/intelligence/src/intelligence/rag/chromaVectorStoreAdapter.js import { Chroma } from "@langchain/community/vectorstores/chroma"; import { OpenAIEmbeddings } from "@langchain/openai"; import { getLogger as getLogger9 } from "@daitanjs/development"; import { getConfigManager as getConfigManager4 } from "@daitanjs/config"; import { DaitanConfigurationError as DaitanConfigurationError2, DaitanOperationError as DaitanOperationError3 } from "@daitanjs/error"; // src/intelligence/src/intelligence/rag/chromaClient.js import { ChromaClient } from "chromadb"; import { getLogger as getLogger8 } from "@daitanjs/development"; import { getConfigManager as getConfigManager3 } from "@daitanjs/config"; import { DaitanOperationError as DaitanOperationError2, DaitanInvalidInputError as DaitanInvalidInputError3 } from "@daitanjs/error"; var logger5 = getLogger8("daitan-rag-chroma-client"); var getChromaHost = () => getConfigManager3().get("CHROMA_HOST", "localhost"); var getChromaPort = () => getConfigManager3().get("CHROMA_PORT", 8e3); var getDefaultCollectionName = () => getConfigManager3().get( "RAG_DEFAULT_COLLECTION_NAME", "daitan_rag_default_store" ); var chromaClientInstance = null; var checkChromaConnection = async (timeoutMs = 3e3) => { const host = getChromaHost(); const port = getChromaPort(); const endpointsToTry = [ `http://${host}:${port}/api/v2/heartbeat`, `http://${host}:${port}/api/v1/heartbeat` ]; for (const url of endpointsToTry) { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeoutMs); const response = await fetch(url, { signal: controller.signal }); clearTimeout(timeoutId); if (response.ok) { logger5.info(`ChromaDB connection successful on endpoint: ${url}`); return true; } } catch (error) { } } logger5.error(`ChromaDB connection check failed on all attempted endpoints.`); return false; }; async function getOrInitializeChromaClient() { if (chromaClientInstance) { try { await chromaClientInstance.heartbeat(); return chromaClientInstance; } catch (error) { logger5.warn( "Existing ChromaDB client connection is stale, re-initializing..." ); chromaClientInstance = null; } } const host = getChromaHost(); const port = getChromaPort(); const baseUrl = `http://${host}:${port}`; try { if (!await checkChromaConnection()) { throw new DaitanOperationError2( `Could not connect to ChromaDB server at ${baseUrl}.` ); } chromaClientInstance = new ChromaClient({ path: baseUrl }); await chromaClientInstance.heartbeat(); logger5.info(`ChromaDB client initialized and connection confirmed.`); } catch (error) { chromaClientInstance = null; throw new DaitanOperationError2( `Failed to initialize ChromaDB client: ${error.message}`, { host, port }, error ); } return chromaClientInstance; } // src/intelligence/src/intelligence/rag/chromaVectorStoreAdapter.js var adapterLogger = getLogger9("daitan-chroma-adapter"); var ChromaVectorStoreAdapter = class { constructor({ collectionName, url, embeddings, verbose }) { if (!collectionName) { throw new DaitanConfigurationError2( "ChromaVectorStoreAdapter: collectionName is required." ); } this.collectionName = collectionName; this.initialConfig = { url, embeddings, verbose }; this.isInitialized = false; this.directClient = null; this.langchainStoreInstance = null; } /** * Initializes the adapter on its first use. This lazy initialization * prevents unnecessary connections and configuration lookups. * @private */ async _lazyInitialize() { if (this.isInitialized) return; const configManager = getConfigManager4(); this.verbose = this.initialConfig.verbose ?? configManager.get("CHROMA_ADAPTER_VERBOSE", false); this.url = this.initialConfig.url || `http://${configManager.get( "CHROMA_HOST", "localhost" )}:${configManager.get("CHROMA_PORT", "8000")}`; this.embeddings = this.initialConfig.embeddings || this._resolveDefaultEmbeddings(configManager); if (!this.embeddings) { throw new DaitanConfigurationError2( "Embeddings must be provided or OpenAI default must be configurable (OPENAI_API_KEY is missing)." ); } this.directClient = await getOrInitializeChromaClient(); this.langchainStoreInstance = new Chroma(this.embeddings, { collectionName: this.collectionName, url: this.url }); this.isInitialized = true; adapterLogger.info( `ChromaVectorStoreAdapter for "${this.collectionName}" has been initialized on first use.` ); } /** * Creates a default OpenAI embeddings instance if none is provided. * @private */ _resolveDefaultEmbeddings(configManager) { const apiKey = configManager.get("OPENAI_API_KEY"); if (!apiKey) { adapterLogger.warn( "Cannot create default OpenAIEmbeddings: OPENAI_API_KEY is not configured." ); return null; } const ragEmbeddingModel = configManager.get( "RAG_EMBEDDING_MODEL_OPENAI", "text-embedding-3-small" ); return new OpenAIEmbeddings({ apiKey, model: ragEmbeddingModel }); } async addDocuments(documents, options = {}) { await this._lazyInitialize(); try { return await this.langchainStoreInstance.addDocuments( documents, options.ids ? { ids: options.ids } : void 0 ); } catch (error) { throw new DaitanOperationError3( `Failed to add documents to collection "${this.collectionName}": ${error.message}`, { collectionName: this.collectionName }, error ); } } async similaritySearchWithScore(query, k = 4, filter) { await this._lazyInitialize(); try { return await this.langchainStoreInstance.similaritySearchWithScore( query, k, filter ); } catch (error) { throw new DaitanOperationError3( `Failed to perform similarity search in collection "${this.collectionName}": ${error.message}`, { collectionName: this.collectionName, query, k }, error ); } } async collectionExists() { await this._lazyInitialize(); try { await this.directClient.getCollection({ name: this.collectionName }); return true; } catch (error) { const errorMessage = String(error.message).toLowerCase(); if (errorMessage.includes("not found") || errorMessage.includes("does not exist")) { return false; } throw new DaitanOperationError3( `Failed to check for collection "${this.collectionName}": ${error.message}`, { host: this.url, collectionName: this.collectionName }, error ); } } async deleteCollection() { await this._lazyInitialize(); try { await this.directClient.deleteCollection({ name: this.collectionName }); this.isInitialized = false; this.langchainStoreInstance = null; adapterLogger.info( `Collection "${this.collectionName}" deleted successfully.` ); } catch (error) { const errorMessage = String(error.message).toLowerCase(); if (!errorMessage.includes("not found") && !errorMessage.includes("does not exist")) { throw new DaitanOperationError3( `Failed to delete Chroma collection "${this.collectionName}": ${error.message}`, { collectionName: this.collectionName }, error ); } adapterLogger.info( `Collection "${this.collectionName}" was already deleted or doesn't exist.` ); } } async getCollectionInfo() { await this._lazyInitialize(); try { const collection = await this.directClient.getCollection({ name: this.collectionName }); const count = await collection.count(); return { name: collection.name, id: collection.id, count, metadata: collection.metadata }; } catch (error) { throw new DaitanOperationError3( `Failed to get collection info for "${this.collectionName}": ${error.message}`, { collectionName: this.collectionName }, error ); } } async clearCollection() { await this._lazyInitialize(); try { const collection = await this.directClient.getCollection({ name: this.collectionName }); const result = await collection.get(); if (result.ids && result.ids.length > 0) { await collection.delete({ ids: result.ids }); adapterLogger.info( `Cleared ${result.ids.length} documents from collection "${this.collectionName}".` ); } else { adapterLogger.info( `Collection "${this.collectionName}" is already empty.` ); } } catch (error) { throw new DaitanOperationError3( `Failed to clear collection "${this.collectionName}": ${error.message}`, { collectionName: this.collectionName }, error ); } } async getLangchainStore() { await this._lazyInitialize(); return this.langchainStoreInstance; } }; // src/intelligence/src/intelligence/rag/memoryVectorStoreAdapter.js import { MemoryVectorStore } from "langchain/vectorstores/memory"; import { OpenAIEmbeddings as OpenAIEmbeddings2 } from "@langchain/openai"; import { getLogger as getLogger10 } from "@daitanjs/development"; import { getConfigManager as getConfigManager5 } from "@daitanjs/config"; import { DaitanConfigurationError as DaitanConfigurationError3, DaitanOperationError as DaitanOperationError4 } from "@daitanjs/error"; import { Document as LangchainDocument } from "@langchain/core/documents"; var memoryAdapterLogger = getLogger10("daitan-memory-vstore-adapter"); var MemoryVectorStoreAdapter = class { /** * @param {Object} [config={}] * @param {import('@langchain/core/embeddings').Embeddings} [config.embeddings] - LangChain embeddings instance. * @param {LangchainDocument[]} [config.initialDocuments=[]] - Optional documents to initialize with. * @param {boolean} [config.verbose] - Verbose logging for this instance. Defaults from ConfigManager. * @throws {DaitanConfigurationError} If embeddings cannot be configured. */ constructor({ embeddings, // Renamed from embeddingsInstance initialDocuments = [], verbose } = {}) { const configManager = getConfigManager5(); this.verbose = verbose !== void 0 ? verbose : configManager.get("MEMORY_ADAPTER_VERBOSE", false) || configManager.get("DEBUG_INTELLIGENCE", false); this.embeddings = embeddings || this._resolveDefaultEmbeddings(); if (!this.embeddings) { const errMsg = "MemoryVectorStoreAdapter: Embeddings must be provided, or OpenAI default embeddings must be configurable (requires OPENAI_API_KEY via ConfigManager)."; memoryAdapterLogger.error(errMsg); throw new DaitanConfigurationError3(errMsg); } this.store = null; if (this.verbose) { memoryAdapterLogger.info( `MemoryVectorStoreAdapter initialized. Verbose: ${this.verbose}. Embeddings: ${this.embeddings.constructor.name}` ); } if (initialDocuments && initialDocuments.length > 0) { this._initializeWithDocuments(initialDocuments).catch((err) => { memoryAdapterLogger.error( `MemoryVectorStoreAdapter: Background initialization with documents failed: ${err.message}` ); }); } } /** * Sets the verbosity for this adapter instance. * @param {boolean} isVerbose */ setVerbose(isVerbose) { this.verbose = isVerbose; memoryAdapterLogger.info( `MemoryVectorStoreAdapter verbosity set to: ${this.verbose}` ); } _resolveDefaultEmbeddings() { const configManager = getConfigManager5(); const apiKey = configManager.get("OPENAI_API_KEY"); if (!apiKey) { memoryAdapterLogger.warn( "MemoryVectorStoreAdapter: OPENAI_API_KEY not found via ConfigManager for default OpenAIEmbeddings." ); return null; } try { const ragEmbeddingModel = configManager.get("RAG_EMBEDDING_MODEL_OPENAI"); const embeddingsConfig = { apiKey }; if (ragEmbeddingModel) { embeddingsConfig.modelName = ragEmbeddingModel; } if (this.verbose) memoryAdapterLogger.debug( `Using OpenAIEmbeddings with model: ${ragEmbeddingModel || "default (text-embedding-ada-002 or similar)"}` ); return new OpenAIEmbeddings2(embeddingsConfig); } catch (e) { memoryAdapterLogger.error( `Failed to instantiate default OpenAIEmbeddings: ${e.message}` ); return null; } } async _initializeWithDocuments(documents) { if (this.store) { if (this.verbose) memoryAdapterLogger.debug( "MemoryVectorStore already initialized, skipping re-initialization with documents." ); return; } try { if (this.verbose) memoryAdapterLogger.debug( `MemoryVectorStore: Initializing via fromDocuments with ${documents.length} documents.` ); this.store = await MemoryVectorStore.fromDocuments( documents, this.embeddings ); if (this.verbose) memoryAdapterLogger.debug( `MemoryVectorStore initialized successfully with ${documents.length} documents.` ); } catch (error) { memoryAdapterLogger.error( `MemoryVectorStoreAdapter: Error during fromDocuments initialization: ${error.message}`, { error_stack: error.stack?.substring(0, 300) } ); } } async _ensureStoreIsInitialized() { if (!this.store) { if (this.verbose) memoryAdapterLogger.debug( "MemoryVectorStoreAdapter: Store not yet initialized. Lazily creating empty store now." ); try { this.store = new MemoryVectorStore(this.embeddings); if (this.verbose) memoryAdapterLogger.debug( "MemoryVectorStoreAdapter: Empty store created successfully." ); } catch (error) { memoryAdapterLogger.error( `MemoryVectorStoreAdapter: Error creating empty MemoryVectorStore instance: ${error.message}`, { error_stack: error.stack?.substring(0, 300) } ); throw new DaitanOperationError4( "Failed to create empty MemoryVectorStore", {}, error ); } } } async addDocuments(documents, options = {}) { if (!Array.isArray(documents) || documents.length === 0) { if (this.verbose) memoryAdapterLogger.info( "MemoryAdapter: No documents provided to add." ); return; } await this._ensureStoreIsInitialized(); if (this.verbose) memoryAdapterLogger.info( `MemoryAdapter: Adding ${documents.length} documents.` ); try { await this.store.addDocuments( documents, options.ids ? { ids: options.ids } : void 0 ); if (this.verbose) memoryAdapterLogger.debug( `MemoryAdapter: Successfully added ${documents.length} documents.` ); } catch (error) { memoryAdapterLogger.error( `MemoryAdapter: Error adding documents: ${error.message}`, { numDocs: documents.length, error_stack: error.stack?.substring(0, 300) } ); throw new DaitanOperationError4( "Failed to add documents to MemoryVectorStore", {}, error ); } } async similaritySearchWithScore(query, k, filter) { await this._ensureStoreIsInitialized(); const logContext = { queryPreview: String(query).substring(0, 70) + "...", k }; if (this.verbose) { memoryAdapterLogger.debug("MemoryAdapter: Similarity search started.", { ...logContext, filter: filter ? typeof filter === "function" ? "Function filter" : filter : "None" }); } let adaptedFilter = filter; if (filter && typeof filter === "object" && !Array.isArray(filter) && typeof filter !== "function") { adaptedFilter = (doc) => { for (const key in filter) { if (!doc.metadata || doc.metadata[key] !== filter[key]) return false; } return true; }; if (this.verbose) memoryAdapterLogger.debug( "MemoryAdapter: Adapted object metadata filter to function for similaritySearch." ); } else if (typeof filter !== "function" && filter !== void 0) { memoryAdapterLogger.warn( "MemoryAdapter: Filter provided is not a metadata object or function. It will be ignored by MemoryVectorStore for similaritySearchWithScore.", { filterType: typeof filter } ); adaptedFilter = void 0; } try { const results = await this.store.similaritySearchWithScore( query, k, adaptedFilter ); if (this.verbose) memoryAdapterLogger.debug( `MemoryAdapter: Similarity search returned ${results.length} results.` ); return results; } catch (error) { memoryAdapterLogger.error( `MemoryAdapter: Similarity search error: ${error.message}`, { ...logContext, error_stack: error.stack?.substring(0, 300) } ); throw new DaitanOperationError4( "Similarity search failed in MemoryVectorStore", logContext, error ); } } async collectionExists(collectionNameIgnored) { await this._ensureStoreIsInitialized(); if (this.verbose) memoryAdapterLogger.debug( `MemoryAdapter: "collectionExists" check - returning ${!!this.store}.` ); return !!this.store; } async ensureCollection(collectionNameIgnored, options = {}) { await this._ensureStoreIsInitialized(); if (this.verbose) memoryAdapterLogger.debug( 'MemoryAdapter: "ensureCollection" called. Store is ready or will be created.' ); } async deleteCollection(collectionNameIgnored) { if (this.verbose) memoryAdapterLogger.info( 'MemoryAdapter: "Deleting collection" - re-initializing to an empty store.' ); try { this.store = new MemoryVectorStore(this.embeddings); if (this.verbose) memoryAdapterLogger.debug( "MemoryAdapter: Store re-initialized (effectively cleared)." ); } catch (error) { memoryAdapterLogger.error( `MemoryAdapter: Error re-initializing store during deleteCollection: ${error.message}`, { error_stack: error.stack?.substring(0, 300) } ); throw new DaitanOperationError4( "Failed to clear/re-initialize MemoryVectorStore", {}, error ); } } async getLangchainStore() { await this._ensureStoreIsInitialized(); return this.store; } }; // src/intelligence/src/intelligence/rag/vectorStoreFactory.js var vectorStoreLogger = getLogger11("daitan-rag-vectorstore"); var FALLBACK_DEFAULT_COLLECTION_NAME = "daitan_rag_default_store"; var DEFAULT_COLLECTION_NAME = () => { const configManager = getConfigManager6(); return configManager.get("RAG_DEFAULT_COLLECTION_NAME") || getDefaultCollectionName() || FALLBACK_DEFAULT_COLLECTION_NAME; }; var currentModuleVerbose = false; var vectorStoreAdapterSingleton = null; var getVectorStore = async ({ persistent, collectionName, forceRecreateCollection = false, embeddingsInstance, chromaUrl, localVerbose } = {}) => { const configManager = getConfigManager6(); const usePersistentStore = persistent ?? configManager.get("RAG_PERSISTENT_STORE", true); const effectiveCollectionName = collectionName || DEFAULT_COLLECTION_NAME(); currentModuleVerbose = localVerbose ?? configManager.get("RAG_MEMORY_VERBOSE", false); const requestedStoreType = usePersistentStore ? "Chroma" : "Memory"; let currentStoreType = vectorStoreAdapterSingleton ? vectorStoreAdapterSingleton instanceof ChromaVectorStoreAdapter ? "Chroma" : "Memory" : null; let needsRecreation = !vectorStoreAdapterSingleton || forceRecreateCollection || requestedStoreType !== currentStoreType || vectorStoreAdapterSingleton.collectionName && // Check if collectionName exists before comparing vectorStoreAdapterSingleton.collectionName !== effectiveCollectionName; if (usePersistentStore && vectorStoreAdapterSingleton instanceof ChromaVectorStoreAdapter && chromaUrl && vectorStoreAdapterSingleton.url !== chromaUrl) { needsRecreation = true; } if (!needsRecreation) { if (currentModuleVerbose) vectorStoreLogger.debug( `Returning existing VectorStoreAdapter for "${effectiveCollectionName}".` ); return vectorStoreAdapterSingleton; } if (currentModuleVerbose) vectorStoreLogger.info( `Re-initializing VectorStoreAdapter. Type: ${requestedStoreType}, Collection: "${effectiveCollectionName}"` ); if (forceRecreateCollection && usePersistentStore) { try { const tempAdapter = new ChromaVectorStoreAdapter({ collectionName: effectiveCollectionName, url: chromaUrl, embeddings: embeddingsInstance }); await tempAdapter.deleteCollection(); } catch (e) { vectorStoreLogger.warn( `Error during pre-emptive deletion of collection "${effectiveCollectionName}": ${e.message}.` ); } } try { vectorStoreAdapterSingleton = usePersistentStore ? new ChromaVectorStoreAdapter({ collectionName: effectiveCollectionName, url: chromaUrl, embeddings: embeddingsInstance, verbose: currentModuleVerbose }) : new MemoryVectorStoreAdapter({ embeddings: embeddingsInstance, verbose: currentModuleVerbose }); return vectorStoreAdapterSingleton; } catch (error) { vectorStoreAdapterSingleton = null; throw new DaitanOperationError5( `Failed to initialize ${requestedStoreType}VectorStoreAdapter for "${effectiveCollectionName}"`, {}, error ); } }; var vectorStoreCollectionExists = async (collectionName) => { const configManager = getConfigManager6(); const usePersistentStore = configManager.get("RAG_PERSISTENT_STORE", true); if (!usePersistentStore) return true; const effectiveCollectionName = collectionName || DEFAULT_COLLECTION_NAME(); try { const tempAdapter = new ChromaVectorStoreAdapter({ collectionName: effectiveCollectionName }); return await tempAdapter.collectionExists(); } catch (error) { vectorStoreLogger.error( `Error checking existence for collection "${effectiveCollectionName}": ${error.message}` ); return false; } }; var embedChunks = async (chunks, options = {}) => { const effectiveCollectionName = options.collectionName || DEFAULT_COLLECTION_NAME(); if (!Array.isArray(chunks) || chunks.length === 0) return; const adapter = await getVectorStore({ ...options, collectionName: effectiveCollectionName }); return adapter.addDocuments(chunks, { ids: options.ids, batchSize: options.batchSize }); }; // src/intelligence/src/intelligence/rag/chatMemor