65 lines
3.3 KiB
TypeScript
65 lines
3.3 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { createQdrantRegulatoryIndexFromEnvironment, REGULATORY_QDRANT_COLLECTION } from '../../server/regulatory/qdrant-regulatory-index';
|
|
import { DenseRegulatoryCandidateRetriever, RegulatoryIngestionService } from '../../server/regulatory/regulatory-rag-service';
|
|
import type { RegulatoryControlDocument } from '../../src/domain/regulatory-rag';
|
|
import type { TextEmbeddingClient } from '../../server/regulatory/embedding-client';
|
|
|
|
const runIntegration = process.env.ASSESSOR_QDRANT_INTEGRATION === '1';
|
|
const describeQdrant = runIntegration ? describe : describe.skip;
|
|
|
|
describeQdrant('Qdrant regulatory index integration', () => {
|
|
it('creates, replaces, filters, and retrieves a synthetic control', async () => {
|
|
const dimensions = Number(process.env.ASSESSOR_RAG_EMBEDDING_DIMENSIONS);
|
|
const model = process.env.ASSESSOR_RAG_EMBEDDING_MODEL ?? '';
|
|
if (!Number.isInteger(dimensions) || dimensions < 64 || !model) throw new Error('Configured embedding dimensions and model are required.');
|
|
const vector = Array.from({ length: dimensions }, (_, index) => index === 0 ? 1 : 0);
|
|
const embeddings: TextEmbeddingClient = { model, dimensions, embed: async texts => texts.map(() => vector) };
|
|
const index = createQdrantRegulatoryIndexFromEnvironment(dimensions, model);
|
|
const ingestion = new RegulatoryIngestionService(embeddings, index);
|
|
const document: RegulatoryControlDocument = {
|
|
schemaVersion: 1,
|
|
corpusId: 'assessor-qdrant-integration',
|
|
corpusVersion: '1',
|
|
documentId: 'synthetic-live-control-de',
|
|
framework: 'ASSESSOR Synthetic Controls',
|
|
frameworkVersion: '1',
|
|
controlId: 'SYN-LIVE',
|
|
language: 'de',
|
|
title: 'Synthetische Livekontrolle',
|
|
text: 'Dieser synthetische Text prueft ausschliesslich den lokalen rebuildbaren Qdrant-Index.'
|
|
};
|
|
|
|
try {
|
|
await expect(ingestion.ingest([document])).resolves.toMatchObject({ documentCount: 1, chunkCount: 1, embeddingModel: model });
|
|
const retrieval = new DenseRegulatoryCandidateRetriever(embeddings, index);
|
|
const candidates = await retrieval.retrieveCandidates({
|
|
query: 'synthetic local index check',
|
|
limit: 3,
|
|
filters: { corpusId: document.corpusId, framework: document.framework, language: 'de' }
|
|
});
|
|
|
|
expect(candidates).toHaveLength(1);
|
|
expect(candidates[0]).toMatchObject({ chunk: {
|
|
corpusId: document.corpusId,
|
|
documentId: document.documentId,
|
|
controlId: document.controlId,
|
|
language: 'de'
|
|
} });
|
|
} finally {
|
|
await deleteSyntheticCorpus(document.corpusId);
|
|
}
|
|
});
|
|
});
|
|
|
|
async function deleteSyntheticCorpus(corpusId: string): Promise<void> {
|
|
const baseUrl = process.env.ASSESSOR_QDRANT_URL;
|
|
const apiKey = process.env.ASSESSOR_QDRANT_API_KEY;
|
|
if (!baseUrl) return;
|
|
const response = await fetch(new URL(`/collections/${REGULATORY_QDRANT_COLLECTION}/points/delete?wait=true`, baseUrl), {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', ...(apiKey ? { 'api-key': apiKey } : {}) },
|
|
body: JSON.stringify({ filter: { must: [{ key: 'corpus_id', match: { value: corpusId } }] } })
|
|
});
|
|
if (!response.ok) throw new Error(`Synthetic Qdrant integration cleanup failed with HTTP ${response.status}.`);
|
|
}
|