WIP - enable embedings
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { TenantDatabaseService } from '../../tenant/tenant-database.service';
|
||||
import { MeilisearchService } from '../../search/meilisearch.service';
|
||||
import { getCentralPrisma } from '../../prisma/central-prisma.service';
|
||||
import { OpenAIConfig } from '../../voice/interfaces/integration-config.interface';
|
||||
import {
|
||||
DefaultSemanticProjectionAdapter,
|
||||
SemanticProjectionAdapter,
|
||||
@@ -12,6 +14,9 @@ import { SemanticLinkService } from './semantic-link.service';
|
||||
export class SemanticOrchestratorService {
|
||||
private readonly logger = new Logger(SemanticOrchestratorService.name);
|
||||
private readonly adapters: SemanticProjectionAdapter[] = [new DefaultSemanticProjectionAdapter()];
|
||||
private readonly defaultEmbeddingModel =
|
||||
process.env.OPENAI_EMBEDDING_MODEL || 'text-embedding-3-small';
|
||||
private readonly semanticEmbedderName = 'semantic-openai';
|
||||
|
||||
constructor(
|
||||
private readonly tenantDbService: TenantDatabaseService,
|
||||
@@ -61,8 +66,9 @@ export class SemanticOrchestratorService {
|
||||
const chunks = this.chunkerService.chunkText(projection.narrative, comments);
|
||||
await this.replaceChunks(knex, documentId, chunks);
|
||||
|
||||
await this.indexChunks(resolvedTenantId, projection, chunks);
|
||||
await this.generateSuggestions(resolvedTenantId, projection, chunks, userId, trigger);
|
||||
const openAiConfig = await this.getOpenAiConfig(resolvedTenantId);
|
||||
await this.indexChunks(resolvedTenantId, projection, chunks, openAiConfig);
|
||||
await this.generateSuggestions(resolvedTenantId, projection, chunks, openAiConfig, userId, trigger);
|
||||
|
||||
return { documentId, chunkCount: chunks.length };
|
||||
}
|
||||
@@ -139,12 +145,25 @@ export class SemanticOrchestratorService {
|
||||
);
|
||||
}
|
||||
|
||||
private async indexChunks(tenantId: string, projection: any, chunks: any[]) {
|
||||
private async indexChunks(
|
||||
tenantId: string,
|
||||
projection: any,
|
||||
chunks: any[],
|
||||
openAiConfig: OpenAIConfig | null,
|
||||
) {
|
||||
if (!this.meilisearchService.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const indexName = this.meilisearchService.buildSemanticChunkIndexName(tenantId);
|
||||
if (openAiConfig?.apiKey) {
|
||||
await this.meilisearchService.ensureOpenAiEmbedder(indexName, {
|
||||
embedderName: this.semanticEmbedderName,
|
||||
apiKey: openAiConfig.apiKey,
|
||||
model: openAiConfig.embeddingModel || this.defaultEmbeddingModel,
|
||||
documentTemplate: '{{doc.title}}\n{{doc.text}}',
|
||||
});
|
||||
}
|
||||
await this.meilisearchService.upsertDocuments(indexName, chunks.map((chunk) => ({
|
||||
id: `${projection.entityType}:${projection.entityId}:${chunk.chunkIndex}`,
|
||||
entityType: projection.entityType,
|
||||
@@ -160,6 +179,7 @@ export class SemanticOrchestratorService {
|
||||
tenantId: string,
|
||||
projection: any,
|
||||
chunks: any[],
|
||||
openAiConfig: OpenAIConfig | null,
|
||||
userId?: string,
|
||||
trigger: string = 'semantic_refresh',
|
||||
) {
|
||||
@@ -169,7 +189,12 @@ export class SemanticOrchestratorService {
|
||||
|
||||
const indexName = this.meilisearchService.buildSemanticChunkIndexName(tenantId);
|
||||
const queryText = chunks.slice(0, 3).map((chunk) => chunk.text).join(' ').slice(0, 1200);
|
||||
const search = await this.meilisearchService.searchIndex(indexName, queryText, 20);
|
||||
const search = await this.meilisearchService.searchIndex(
|
||||
indexName,
|
||||
queryText,
|
||||
20,
|
||||
openAiConfig?.apiKey ? { embedder: this.semanticEmbedderName } : undefined,
|
||||
);
|
||||
|
||||
const grouped = new Map<string, any[]>();
|
||||
for (const hit of search.hits || []) {
|
||||
@@ -222,4 +247,38 @@ export class SemanticOrchestratorService {
|
||||
|
||||
return `${objectDefinition.apiName.toLowerCase()}s`;
|
||||
}
|
||||
|
||||
private async getOpenAiConfig(tenantId: string): Promise<OpenAIConfig | null> {
|
||||
const resolvedTenantId = await this.tenantDbService.resolveTenantId(tenantId);
|
||||
const centralPrisma = getCentralPrisma();
|
||||
const tenant = await centralPrisma.tenant.findUnique({
|
||||
where: { id: resolvedTenantId },
|
||||
select: { integrationsConfig: true },
|
||||
});
|
||||
|
||||
let config = tenant?.integrationsConfig
|
||||
? typeof tenant.integrationsConfig === 'string'
|
||||
? this.tenantDbService.decryptIntegrationsConfig(tenant.integrationsConfig)
|
||||
: tenant.integrationsConfig
|
||||
: null;
|
||||
|
||||
if (!config?.openai && process.env.OPENAI_API_KEY) {
|
||||
config = {
|
||||
...(config || {}),
|
||||
openai: {
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
embeddingModel: this.defaultEmbeddingModel,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (config?.openai?.apiKey) {
|
||||
return {
|
||||
apiKey: config.openai.apiKey,
|
||||
embeddingModel: config.openai.embeddingModel || this.defaultEmbeddingModel,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user