// src/services/ai.ts // Copyright (C) 2025 DTP Technologies, LLC // All Rights Reserved import { AiProvider, type ApiType, type IAiModelSettings, } from "../models/ai-provider.js"; import { User } from "../models/user.js"; import { OllamaAiClient } from "../lib/ai-clients/ollama-client.js"; import { OpenAiClient } from "../lib/ai-clients/openai-client.js"; import type { AiClient, ChatOptions } from "../lib/ai-client.js"; import { DtpService } from "../lib/service.js"; interface ProviderData { apiType: ApiType; baseUrl: string; apiKey: string; } class AiService extends DtpService { get name(): string { return "AiService"; } get slug(): string { return "ai"; } constructor() { super(); } async start(): Promise { this.log.info("service started"); } async stop(): Promise { this.log.info("service stopped"); } getClient(provider: ProviderData): AiClient { if (provider.apiType === "ollama") { return new OllamaAiClient(provider.baseUrl, provider.apiKey); } return new OpenAiClient(provider.baseUrl, provider.apiKey); } async getAgentClient(userId: string): Promise { const user = await User.findById(userId) .select("+connections.ai.agentProviderId +connections.ai.agentModel") .lean(); if (!user) { throw new Error("User not found"); } const aiConfig = user.connections?.ai; if (!aiConfig?.agentProviderId) { throw new Error("No agent provider configured"); } const provider = await AiProvider.findById(aiConfig.agentProviderId) .select("+apiKey") .lean(); if (!provider) { throw new Error("Agent provider not found"); } return this.getClient({ apiType: provider.apiType, baseUrl: provider.baseUrl, apiKey: provider.apiKey, }); } async getAgentProviderInfo( userId: string, ): Promise<{ providerId: string; model: string } | null> { const user = await User.findById(userId) .select("+connections.ai.agentProviderId +connections.ai.agentModel") .lean(); if (!user) { return null; } const aiConfig = user.connections?.ai; if (!aiConfig?.agentProviderId || !aiConfig?.agentModel) { return null; } return { providerId: aiConfig.agentProviderId.toString(), model: aiConfig.agentModel, }; } async getVectorClient(userId: string): Promise { const user = await User.findById(userId) .select("+connections.ai.vectorProviderId +connections.ai.vectorModel") .lean(); if (!user) { throw new Error("User not found"); } const aiConfig = user.connections?.ai; if (!aiConfig?.vectorProviderId) { throw new Error("No vector provider configured"); } const provider = await AiProvider.findById(aiConfig.vectorProviderId) .select("+apiKey") .lean(); if (!provider) { throw new Error("Vector provider not found"); } return this.getClient({ apiType: provider.apiType, baseUrl: provider.baseUrl, apiKey: provider.apiKey, }); } async getUtilityClient(userId: string): Promise { const user = await User.findById(userId) .select("+connections.ai.utilityProviderId +connections.ai.utilityModel") .lean(); if (!user) { throw new Error("User not found"); } const aiConfig = user.connections?.ai; if (!aiConfig?.utilityProviderId) { throw new Error("No utility provider configured"); } const provider = await AiProvider.findById(aiConfig.utilityProviderId) .select("+apiKey") .lean(); if (!provider) { throw new Error("Utility provider not found"); } return this.getClient({ apiType: provider.apiType, baseUrl: provider.baseUrl, apiKey: provider.apiKey, }); } async getAgentModel(userId: string): Promise { const user = await User.findById(userId) .select("+connections.ai.agentModel") .lean(); if (!user) { throw new Error("User not found"); } return user.connections?.ai?.agentModel || ""; } async getVectorModel(userId: string): Promise { const user = await User.findById(userId) .select("+connections.ai.vectorModel") .lean(); if (!user) { throw new Error("User not found"); } return user.connections?.ai?.vectorModel || ""; } async getUtilityModel(userId: string): Promise { const user = await User.findById(userId) .select("+connections.ai.utilityModel") .lean(); if (!user) { throw new Error("User not found"); } return user.connections?.ai?.utilityModel || ""; } async getModelSettings( userId: string, providerId: string, modelId: string, ): Promise { const provider = await AiProvider.findOne({ _id: providerId, user: userId, }).lean(); if (!provider) { return null; } const model = provider.models.find((m) => m.id === modelId); return model?.settings ?? null; } async getModelChatOptions( userId: string, providerId: string, modelId: string, ): Promise { const provider = await AiProvider.findOne({ _id: providerId, user: userId, }).lean(); if (!provider) { return {}; } const model = provider.models.find((m) => m.id === modelId); const settings = model?.settings; if (!settings) { const defaults = this.getDefaultOptions( provider.apiType, model?.contextWindow, ); return defaults; } return { temperature: settings.temperature, topP: settings.topP, topK: settings.topK, numCtx: settings.numCtx, }; } private getDefaultOptions( apiType: ApiType, contextWindow?: number, ): ChatOptions { if (apiType === "ollama") { return { temperature: 0.8, topP: 0.9, topK: 40, numCtx: contextWindow || 2048, }; } return { temperature: 0.7, topP: 1.0, }; } async isConfigured(userId: string): Promise { const user = await User.findById(userId) .select( "+connections.ai.agentProviderId +connections.ai.vectorProviderId +connections.ai.utilityProviderId", ) .lean(); if (!user) { return false; } const aiConfig = user.connections?.ai; return !!( aiConfig?.agentProviderId && aiConfig?.vectorProviderId && aiConfig?.utilityProviderId ); } async getModelDisplay( userId: string, slot: "agent" | "vector" | "util", ): Promise { const user = await User.findById(userId) .select( "+connections.ai.agentModel +connections.ai.agentProviderId +connections.ai.vectorModel +connections.ai.vectorProviderId +connections.ai.utilityModel +connections.ai.utilityProviderId", ) .lean(); if (!user) return "N/A"; const aiConfig = user.connections?.ai; let model = ""; let providerId: string | null = null; if (slot === "agent") { model = aiConfig?.agentModel || ""; providerId = aiConfig?.agentProviderId?.toString() || null; } else if (slot === "vector") { model = aiConfig?.vectorModel || ""; providerId = aiConfig?.vectorProviderId?.toString() || null; } else { model = aiConfig?.utilityModel || ""; providerId = aiConfig?.utilityProviderId?.toString() || null; } if (!model || !providerId) return "N/A"; const provider = await AiProvider.findById(providerId) .select("name") .lean(); const providerName = provider?.name || "Unknown"; return `${model} (${providerName})`; } } export default new AiService();