308 lines
7.6 KiB
TypeScript
308 lines
7.6 KiB
TypeScript
// 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<void> {
|
|
this.log.info("service started");
|
|
}
|
|
|
|
async stop(): Promise<void> {
|
|
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<AiClient> {
|
|
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<AiClient> {
|
|
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<AiClient> {
|
|
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<string> {
|
|
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<string> {
|
|
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<string> {
|
|
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<IAiModelSettings | null> {
|
|
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<ChatOptions> {
|
|
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<boolean> {
|
|
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<string> {
|
|
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();
|