@retrivora-ai/rag-engine 0.4.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -57
- package/dist/{ChromaDBProvider-GI7TB7GJ.mjs → ChromaDBProvider-APQVJ5F7.mjs} +2 -2
- package/dist/{DocumentChunker-3yElxTO3.d.mts → DocumentChunker-C-sCZPhi.d.mts} +6 -6
- package/dist/{DocumentChunker-3yElxTO3.d.ts → DocumentChunker-C-sCZPhi.d.ts} +6 -6
- package/dist/{MilvusProvider-WDVTFB7D.mjs → MilvusProvider-35US67MS.mjs} +2 -2
- package/dist/{MongoDBProvider-ZKW34AEL.mjs → MongoDBProvider-COVYZDP6.mjs} +2 -2
- package/dist/{PineconeProvider-BE2JWSPD.mjs → PineconeProvider-AWFJQDZL.mjs} +2 -2
- package/dist/{PostgreSQLProvider-5HHTK4SU.mjs → PostgreSQLProvider-IEYRJ7XJ.mjs} +2 -2
- package/dist/{QdrantProvider-XVDVBNIG.mjs → QdrantProvider-M6TQYZRO.mjs} +2 -2
- package/dist/{RagConfig-BgRDL9Vy.d.mts → RagConfig-DRJO4hGU.d.mts} +12 -1
- package/dist/{RagConfig-BgRDL9Vy.d.ts → RagConfig-DRJO4hGU.d.ts} +12 -1
- package/dist/{RedisProvider-EK2R2PQH.mjs → RedisProvider-3G5PBLZ4.mjs} +2 -2
- package/dist/{SimpleGraphProvider-M6T7SE7D.mjs → SimpleGraphProvider-UK7DJW37.mjs} +1 -1
- package/dist/{UniversalVectorProvider-YIDRX6VT.mjs → UniversalVectorProvider-FYQ3B2PW.mjs} +3 -3
- package/dist/{WeaviateProvider-4CAPQ7UY.mjs → WeaviateProvider-ITHO36IL.mjs} +2 -2
- package/dist/{chunk-5KNBWQM6.mjs → chunk-4A47RCG2.mjs} +5 -1
- package/dist/{chunk-EDLTMSNY.mjs → chunk-67AJ6SMD.mjs} +1 -1
- package/dist/{chunk-IWHCAQEA.mjs → chunk-7SOSCZGS.mjs} +68 -7
- package/dist/{chunk-LJWWPTWE.mjs → chunk-FLOSGE6A.mjs} +76 -14
- package/dist/{chunk-H6RKMU7W.mjs → chunk-NXUCKY5L.mjs} +1 -1
- package/dist/{chunk-KTS3LLHY.mjs → chunk-OOQXNLXD.mjs} +5 -5
- package/dist/{chunk-OKY5P6RA.mjs → chunk-P4HAQ7KB.mjs} +1186 -1345
- package/dist/chunk-QMIKLALV.mjs +57 -0
- package/dist/{chunk-3QWAK3RZ.mjs → chunk-TYHTZIDP.mjs} +6 -2
- package/dist/{chunk-GQT5LF4G.mjs → chunk-U6KHVZLF.mjs} +2 -2
- package/dist/{chunk-RK2UDJA2.mjs → chunk-WGSZNY3X.mjs} +1 -1
- package/dist/{chunk-XCNXPECE.mjs → chunk-ZNBKHNJ4.mjs} +55 -1
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +1417 -1377
- package/dist/handlers/index.mjs +3 -3
- package/dist/index-CrGMwXfO.d.ts +112 -0
- package/dist/index-v669iV-k.d.mts +112 -0
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.mjs +2 -2
- package/dist/server.d.mts +104 -158
- package/dist/server.d.ts +104 -158
- package/dist/server.js +1419 -1379
- package/dist/server.mjs +12 -12
- package/package.json +5 -1
- package/src/components/DocumentUpload.tsx +1 -1
- package/src/config/RagConfig.ts +7 -0
- package/src/core/ConfigValidator.ts +66 -492
- package/src/core/LangChainAgent.ts +78 -0
- package/src/core/Pipeline.ts +210 -222
- package/src/core/ProviderHealthCheck.ts +35 -406
- package/src/core/ProviderInterfaces.ts +37 -0
- package/src/core/ProviderRegistry.ts +70 -55
- package/src/core/QueryProcessor.ts +173 -0
- package/src/llm/ILLMProvider.ts +10 -0
- package/src/llm/LLMFactory.ts +33 -13
- package/src/llm/providers/AnthropicProvider.ts +55 -15
- package/src/llm/providers/GeminiProvider.ts +51 -0
- package/src/llm/providers/OllamaProvider.ts +100 -15
- package/src/llm/providers/OpenAIProvider.ts +60 -11
- package/src/providers/vectordb/BaseVectorProvider.ts +11 -0
- package/src/providers/vectordb/MilvusProvider.ts +4 -0
- package/src/providers/vectordb/MongoDBProvider.ts +75 -11
- package/src/providers/vectordb/PineconeProvider.ts +60 -5
- package/src/providers/vectordb/PostgreSQLProvider.ts +84 -14
- package/src/providers/vectordb/QdrantProvider.ts +4 -0
- package/src/providers/vectordb/WeaviateProvider.ts +8 -4
- package/src/rag/DocumentChunker.ts +15 -19
- package/src/rag/EntityExtractor.ts +1 -1
- package/src/rag/LlamaIndexIngestor.ts +61 -0
- package/src/rag/Reranker.ts +20 -0
- package/src/server.ts +1 -1
- package/src/types/index.ts +9 -0
- package/src/utils/DocumentParser.ts +1 -1
- package/dist/chunk-FWCSY2DS.mjs +0 -37
- package/dist/index-7qeLTPBL.d.mts +0 -114
- package/dist/index-DowY4_K0.d.ts +0 -114
|
@@ -3,123 +3,138 @@ import { BaseGraphProvider } from '@/providers/graphdb/BaseGraphProvider';
|
|
|
3
3
|
import { VectorDBConfig, GraphDBConfig, LLMConfig, EmbeddingConfig } from '../config/RagConfig';
|
|
4
4
|
import { LLMFactory } from '../llm/LLMFactory';
|
|
5
5
|
import { ILLMProvider } from '../llm/ILLMProvider';
|
|
6
|
+
import { IProviderValidator, IProviderHealthChecker } from './ProviderInterfaces';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* ProviderRegistry — dynamic provider loader for Vector DBs and LLMs.
|
|
9
|
-
*
|
|
10
|
-
* Supports:
|
|
11
|
-
* - Built-in providers loaded on-demand via dynamic imports (tree-shaking friendly)
|
|
12
|
-
* - Custom provider registration via registerVectorProvider()
|
|
13
10
|
*/
|
|
11
|
+
type VectorProviderClass = {
|
|
12
|
+
new (config: VectorDBConfig): BaseVectorProvider;
|
|
13
|
+
getValidator?: () => IProviderValidator;
|
|
14
|
+
getHealthChecker?: () => IProviderHealthChecker;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type GraphProviderClass = new (config: GraphDBConfig) => BaseGraphProvider;
|
|
18
|
+
|
|
14
19
|
export class ProviderRegistry {
|
|
15
|
-
private static vectorProviders: Record<string,
|
|
16
|
-
private static graphProviders: Record<string,
|
|
20
|
+
private static vectorProviders: Record<string, VectorProviderClass> = {};
|
|
21
|
+
private static graphProviders: Record<string, GraphProviderClass> = {};
|
|
22
|
+
|
|
23
|
+
private static vectorValidators: Record<string, IProviderValidator> = {};
|
|
24
|
+
private static vectorHealthCheckers: Record<string, IProviderHealthChecker> = {};
|
|
25
|
+
|
|
26
|
+
private static llmValidators: Record<string, IProviderValidator> = {};
|
|
27
|
+
private static llmHealthCheckers: Record<string, IProviderHealthChecker> = {};
|
|
17
28
|
|
|
18
|
-
static registerVectorProvider(name: string, providerClass:
|
|
29
|
+
static registerVectorProvider(name: string, providerClass: VectorProviderClass) {
|
|
19
30
|
this.vectorProviders[name] = providerClass;
|
|
31
|
+
if (providerClass.getValidator) {
|
|
32
|
+
this.vectorValidators[name] = providerClass.getValidator();
|
|
33
|
+
}
|
|
34
|
+
if (providerClass.getHealthChecker) {
|
|
35
|
+
this.vectorHealthCheckers[name] = providerClass.getHealthChecker();
|
|
36
|
+
}
|
|
20
37
|
}
|
|
21
38
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
static async getVectorValidator(provider: string): Promise<IProviderValidator | null> {
|
|
40
|
+
if (this.vectorValidators[provider]) return this.vectorValidators[provider];
|
|
41
|
+
|
|
42
|
+
// Try to load built-in validator
|
|
43
|
+
try {
|
|
44
|
+
const providerClass = await this.loadVectorProviderClass(provider);
|
|
45
|
+
if (providerClass.getValidator) {
|
|
46
|
+
this.vectorValidators[provider] = providerClass.getValidator();
|
|
47
|
+
return this.vectorValidators[provider];
|
|
48
|
+
}
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.warn(`[ProviderRegistry] Failed to load validator for ${provider}:`, e);
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
27
53
|
}
|
|
28
54
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
55
|
+
static async getVectorHealthChecker(provider: string): Promise<IProviderHealthChecker | null> {
|
|
56
|
+
if (this.vectorHealthCheckers[provider]) return this.vectorHealthCheckers[provider];
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const providerClass = await this.loadVectorProviderClass(provider);
|
|
60
|
+
if (providerClass.getHealthChecker) {
|
|
61
|
+
this.vectorHealthCheckers[provider] = providerClass.getHealthChecker();
|
|
62
|
+
return this.vectorHealthCheckers[provider];
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.warn(`[ProviderRegistry] Failed to load health checker for ${provider}:`, e);
|
|
39
66
|
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private static async loadVectorProviderClass(provider: string): Promise<VectorProviderClass> {
|
|
71
|
+
if (this.vectorProviders[provider]) return this.vectorProviders[provider];
|
|
40
72
|
|
|
41
|
-
// Built-in providers — lazy-loaded
|
|
42
73
|
switch (provider) {
|
|
43
74
|
case 'pinecone': {
|
|
44
75
|
const { PineconeProvider } = await import('../providers/vectordb/PineconeProvider');
|
|
45
|
-
return
|
|
76
|
+
return PineconeProvider;
|
|
46
77
|
}
|
|
47
78
|
case 'pgvector':
|
|
48
79
|
case 'postgresql': {
|
|
49
80
|
const { PostgreSQLProvider } = await import('../providers/vectordb/PostgreSQLProvider');
|
|
50
|
-
return
|
|
81
|
+
return PostgreSQLProvider;
|
|
51
82
|
}
|
|
52
83
|
case 'mongodb': {
|
|
53
84
|
const { MongoDBProvider } = await import('../providers/vectordb/MongoDBProvider');
|
|
54
|
-
return
|
|
85
|
+
return MongoDBProvider;
|
|
55
86
|
}
|
|
56
87
|
case 'milvus': {
|
|
57
88
|
const { MilvusProvider } = await import('../providers/vectordb/MilvusProvider');
|
|
58
|
-
return
|
|
89
|
+
return MilvusProvider;
|
|
59
90
|
}
|
|
60
91
|
case 'qdrant': {
|
|
61
92
|
const { QdrantProvider } = await import('../providers/vectordb/QdrantProvider');
|
|
62
|
-
return
|
|
93
|
+
return QdrantProvider;
|
|
63
94
|
}
|
|
64
95
|
case 'chromadb': {
|
|
65
96
|
const { ChromaDBProvider } = await import('../providers/vectordb/ChromaDBProvider');
|
|
66
|
-
return
|
|
97
|
+
return ChromaDBProvider;
|
|
67
98
|
}
|
|
68
99
|
case 'redis': {
|
|
69
100
|
const { RedisProvider } = await import('../providers/vectordb/RedisProvider');
|
|
70
|
-
return
|
|
101
|
+
return RedisProvider;
|
|
71
102
|
}
|
|
72
103
|
case 'weaviate': {
|
|
73
104
|
const { WeaviateProvider } = await import('../providers/vectordb/WeaviateProvider');
|
|
74
|
-
return
|
|
105
|
+
return WeaviateProvider;
|
|
75
106
|
}
|
|
76
107
|
case 'universal_rest':
|
|
77
108
|
case 'rest': {
|
|
78
109
|
const { UniversalVectorProvider } = await import('../providers/vectordb/UniversalVectorProvider');
|
|
79
|
-
return
|
|
110
|
+
return UniversalVectorProvider;
|
|
80
111
|
}
|
|
81
112
|
default:
|
|
82
|
-
throw new Error(
|
|
83
|
-
`[ProviderRegistry] Unsupported vector provider: "${provider}". ` +
|
|
84
|
-
`Built-in providers: pinecone | pgvector | postgresql | mongodb | milvus | qdrant | chromadb | redis | weaviate | universal_rest. ` +
|
|
85
|
-
`For custom providers, call ProviderRegistry.registerVectorProvider("${provider}", YourClass).`
|
|
86
|
-
);
|
|
113
|
+
throw new Error(`Unsupported vector provider: ${provider}`);
|
|
87
114
|
}
|
|
88
115
|
}
|
|
89
116
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
117
|
+
static async createVectorProvider(config: VectorDBConfig): Promise<BaseVectorProvider> {
|
|
118
|
+
const providerClass = await this.loadVectorProviderClass(config.provider);
|
|
119
|
+
return new providerClass(config);
|
|
120
|
+
}
|
|
121
|
+
|
|
93
122
|
static async createGraphProvider(config: GraphDBConfig): Promise<BaseGraphProvider> {
|
|
94
123
|
const { provider } = config;
|
|
95
|
-
|
|
96
|
-
// Custom registered provider takes priority
|
|
97
124
|
if (this.graphProviders[provider]) {
|
|
98
125
|
return new this.graphProviders[provider](config);
|
|
99
126
|
}
|
|
100
127
|
|
|
101
|
-
// Built-in providers — lazy-loaded
|
|
102
128
|
switch (provider) {
|
|
103
|
-
case 'neo4j': {
|
|
104
|
-
// Placeholder for real Neo4j provider
|
|
105
|
-
throw new Error('[ProviderRegistry] Neo4j provider not implemented yet.');
|
|
106
|
-
}
|
|
107
129
|
case 'simple': {
|
|
108
130
|
const { SimpleGraphProvider } = await import('@/providers/graphdb/SimpleGraphProvider');
|
|
109
131
|
return new SimpleGraphProvider(config);
|
|
110
132
|
}
|
|
111
133
|
default:
|
|
112
|
-
throw new Error(
|
|
113
|
-
`[ProviderRegistry] Unsupported graph provider: "${provider}". ` +
|
|
114
|
-
`Built-in providers: simple. ` +
|
|
115
|
-
`For custom providers, call ProviderRegistry.registerGraphProvider("${provider}", YourClass).`
|
|
116
|
-
);
|
|
134
|
+
throw new Error(`Unsupported graph provider: ${provider}`);
|
|
117
135
|
}
|
|
118
136
|
}
|
|
119
137
|
|
|
120
|
-
/**
|
|
121
|
-
* Creates an LLM provider based on the configuration.
|
|
122
|
-
*/
|
|
123
138
|
static createLLMProvider(llmConfig: LLMConfig, embeddingConfig?: EmbeddingConfig): ILLMProvider {
|
|
124
139
|
return LLMFactory.create(llmConfig, embeddingConfig);
|
|
125
140
|
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryProcessor.ts — Utilities for pre-processing user questions.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Extracting semantic hints from natural language (emails, IDs, quoted phrases).
|
|
6
|
+
* - Normalizing text values for consistent filtering.
|
|
7
|
+
* - Building structured query filters for vector databases.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface QueryFieldHint {
|
|
11
|
+
field?: string;
|
|
12
|
+
value: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface QueryFilter {
|
|
16
|
+
metadata?: Record<string, string>;
|
|
17
|
+
keywords?: string[];
|
|
18
|
+
queryText?: string;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class QueryProcessor {
|
|
23
|
+
/**
|
|
24
|
+
* Normalizes a string value by collapsing whitespace and trimming.
|
|
25
|
+
*/
|
|
26
|
+
static normalizeHintValue(value: string): string {
|
|
27
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Checks if a string is likely a question word or common prompt phrase.
|
|
32
|
+
*/
|
|
33
|
+
static isLikelyPromptPhrase(value: string): boolean {
|
|
34
|
+
return /^(what|which|who|where|when|why|how)\b/i.test(value.trim());
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Scans a natural language question for potential metadata hints and keywords.
|
|
39
|
+
*/
|
|
40
|
+
static extractQueryFieldHints(question: string): QueryFieldHint[] {
|
|
41
|
+
if (!question.trim()) return [];
|
|
42
|
+
|
|
43
|
+
const hints = new Map<string, QueryFieldHint>();
|
|
44
|
+
|
|
45
|
+
const addHint = (value: string, field?: string) => {
|
|
46
|
+
const normalizedValue = this.normalizeHintValue(value);
|
|
47
|
+
if (!normalizedValue) return;
|
|
48
|
+
|
|
49
|
+
const normalizedField = field
|
|
50
|
+
? field
|
|
51
|
+
.toLowerCase()
|
|
52
|
+
.replace(/[^a-z0-9]+/g, ' ')
|
|
53
|
+
.trim()
|
|
54
|
+
: undefined;
|
|
55
|
+
|
|
56
|
+
const key = `${normalizedField ?? '*'}::${normalizedValue.toLowerCase()}`;
|
|
57
|
+
if (!hints.has(key)) {
|
|
58
|
+
hints.set(key, {
|
|
59
|
+
value: normalizedValue,
|
|
60
|
+
...(normalizedField ? { field: normalizedField } : {}),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// 1. Quoted phrases
|
|
66
|
+
for (const match of question.matchAll(/["']([^"']{2,100})["']/g)) {
|
|
67
|
+
addHint(match[1]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 2. Natural question patterns
|
|
71
|
+
const naturalQuestionPatterns = [
|
|
72
|
+
/\b(?:what|which)\s+(?:is|are|was|were)\s+(?:the\s+)?([^?.!,]{1,60}?)\s+of\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
|
|
73
|
+
/\b(?:who|what)\s+(?:is|are|was|were)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
|
|
74
|
+
/\b(?:about|for|regarding)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
// 3. Person/Company targeted patterns
|
|
78
|
+
const personCompanyPatterns = [
|
|
79
|
+
/\bcompany(?:\s+name)?\s+(?:of|for)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
|
|
80
|
+
/\b(?:which|what)\s+company\s+does\s+["']?([^"'\n?.!,]{2,120})["']?\s+work(?:\s+for|\s+at)?(?=[?.!,]|$)/gi,
|
|
81
|
+
/\bwhere\s+does\s+["']?([^"'\n?.!,]{2,120})["']?\s+work(?:\s+for|\s+at)?(?=[?.!,]|$)/gi,
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
for (const pattern of personCompanyPatterns) {
|
|
85
|
+
for (const match of question.matchAll(pattern)) {
|
|
86
|
+
const name = match[1];
|
|
87
|
+
if (name) addHint(name, 'name');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 4. Universal patterns (email, phone, etc.)
|
|
92
|
+
const universalPatterns: Array<{ regex: RegExp; field?: string; group?: number }> = [
|
|
93
|
+
{ regex: /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/gi, field: 'email', group: 1 },
|
|
94
|
+
{ regex: /(\+?\d[\d\-\.\s\(\)]{6,}\d)/g, field: 'phone', group: 1 },
|
|
95
|
+
{ regex: /(\b\d{4}-\d{2}-\d{2}\b|\b\d{1,2}\/\d{1,2}\/\d{2,4}\b)/g, field: 'date', group: 1 },
|
|
96
|
+
{ regex: /(\$\s?\d{1,3}(?:,\d{3})*(?:\.\d+)?)/g, field: 'amount', group: 1 },
|
|
97
|
+
{ regex: /\b(ID|id|identifier)[: ]\s*([A-Za-z0-9\-]{3,})\b/gi, field: 'id', group: 2 },
|
|
98
|
+
{ regex: /"([^"]{2,120})"/g, group: 1 },
|
|
99
|
+
{ regex: /'([^']{2,120})'/g, group: 1 },
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
for (const p of universalPatterns) {
|
|
103
|
+
for (const match of question.matchAll(p.regex)) {
|
|
104
|
+
const val = p.group ? match[p.group] ?? match[0] : match[0];
|
|
105
|
+
if (!val) continue;
|
|
106
|
+
if (p.field) addHint(val, p.field);
|
|
107
|
+
else addHint(val);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const pattern of naturalQuestionPatterns) {
|
|
112
|
+
for (const match of question.matchAll(pattern)) {
|
|
113
|
+
const value = match[2] ?? match[1];
|
|
114
|
+
if (value) addHint(value);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 5. Explicit field-value pairs (e.g., status=active, priority:high)
|
|
119
|
+
const fieldPattern = `([^\\n:=?.!,]{1,60}?)`;
|
|
120
|
+
const valuePattern = `([^\\n?.!,]{1,120}?)`;
|
|
121
|
+
const fieldValuePatterns = [
|
|
122
|
+
new RegExp(`\\b${fieldPattern}\\s*(?:=|:)\\s*["']?${valuePattern}["']?(?=[?.!,]|$)`, 'gi'),
|
|
123
|
+
new RegExp(`\\b${fieldPattern}\\s+(?:is|are|was|were|equals?|equal to|named|called)\\s+["']?${valuePattern}["']?(?=[?.!,]|$)`, 'gi'),
|
|
124
|
+
new RegExp(`\\bwith\\s+${fieldPattern}\\s+["']?${valuePattern}["']?(?=[?.!,]|$)`, 'gi'),
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
for (const pattern of fieldValuePatterns) {
|
|
128
|
+
for (const match of question.matchAll(pattern)) {
|
|
129
|
+
const field = this.normalizeHintValue(match[1] ?? '');
|
|
130
|
+
const value = match[2] ?? '';
|
|
131
|
+
|
|
132
|
+
if (field && !this.isLikelyPromptPhrase(field)) {
|
|
133
|
+
addHint(value, field);
|
|
134
|
+
} else {
|
|
135
|
+
addHint(value);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 6. Capitalized proper nouns
|
|
141
|
+
for (const match of question.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,3}\b/g)) {
|
|
142
|
+
addHint(match[0]);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return [...hints.values()];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Constructs a QueryFilter object from extracted hints.
|
|
150
|
+
*/
|
|
151
|
+
static buildQueryFilter(question: string, hints: QueryFieldHint[]): QueryFilter {
|
|
152
|
+
const filter: QueryFilter = { metadata: {}, keywords: [], queryText: question };
|
|
153
|
+
|
|
154
|
+
for (const hint of hints) {
|
|
155
|
+
if (hint.field) {
|
|
156
|
+
filter.metadata![hint.field] = hint.value;
|
|
157
|
+
} else {
|
|
158
|
+
filter.keywords!.push(hint.value);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Capture proper-noun sequences as keywords
|
|
163
|
+
for (const match of question.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,3}\b/g)) {
|
|
164
|
+
const term = this.normalizeHintValue(match[0]);
|
|
165
|
+
if (term && !filter.keywords!.includes(term)) filter.keywords!.push(term);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (Object.keys(filter.metadata || {}).length === 0) delete filter.metadata;
|
|
169
|
+
if (filter.keywords && filter.keywords.length === 0) delete filter.keywords;
|
|
170
|
+
|
|
171
|
+
return filter;
|
|
172
|
+
}
|
|
173
|
+
}
|
package/src/llm/ILLMProvider.ts
CHANGED
|
@@ -39,6 +39,16 @@ export interface ILLMProvider {
|
|
|
39
39
|
options?: ChatOptions
|
|
40
40
|
): Promise<string>;
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Send a streaming chat completion request.
|
|
44
|
+
* @returns – an async iterable of text chunks
|
|
45
|
+
*/
|
|
46
|
+
chatStream?(
|
|
47
|
+
messages: ChatMessage[],
|
|
48
|
+
context: string,
|
|
49
|
+
options?: ChatOptions
|
|
50
|
+
): AsyncIterable<string>;
|
|
51
|
+
|
|
42
52
|
/**
|
|
43
53
|
* Generate an embedding vector for the given text.
|
|
44
54
|
* @param text – text to embed
|
package/src/llm/LLMFactory.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LLMFactory — instantiates the correct ILLMProvider based on llmConfig.provider.
|
|
3
|
-
*
|
|
4
|
-
* Also accepts an optional EmbeddingConfig so providers that support embedding
|
|
5
|
-
* can be initialised with separate embedding credentials.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import { ILLMProvider } from './ILLMProvider';
|
|
9
2
|
import { LLMConfig, EmbeddingConfig, LLMProvider } from '../config/RagConfig';
|
|
10
3
|
import { OpenAIProvider } from './providers/OpenAIProvider';
|
|
@@ -12,7 +5,16 @@ import { AnthropicProvider } from './providers/AnthropicProvider';
|
|
|
12
5
|
import { OllamaProvider } from './providers/OllamaProvider';
|
|
13
6
|
import { GeminiProvider } from './providers/GeminiProvider';
|
|
14
7
|
import { UniversalLLMAdapter } from './providers/UniversalLLMAdapter';
|
|
8
|
+
import { IProviderValidator, IProviderHealthChecker } from '../core/ProviderInterfaces';
|
|
15
9
|
|
|
10
|
+
interface LLMProviderStatic {
|
|
11
|
+
getValidator?: () => IProviderValidator;
|
|
12
|
+
getHealthChecker?: () => IProviderHealthChecker;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* LLMFactory — instantiates the correct ILLMProvider based on llmConfig.provider.
|
|
17
|
+
*/
|
|
16
18
|
export class LLMFactory {
|
|
17
19
|
static create(llmConfig: LLMConfig, embeddingConfig?: EmbeddingConfig): ILLMProvider {
|
|
18
20
|
switch (llmConfig.provider) {
|
|
@@ -29,20 +31,38 @@ export class LLMFactory {
|
|
|
29
31
|
case 'custom':
|
|
30
32
|
return new UniversalLLMAdapter(llmConfig);
|
|
31
33
|
default:
|
|
32
|
-
// If baseUrl is provided but provider is unknown, fallback to Universal Adapter
|
|
33
34
|
if (llmConfig.baseUrl || (llmConfig.options as Record<string, unknown>)?.baseUrl) {
|
|
34
35
|
return new UniversalLLMAdapter(llmConfig);
|
|
35
36
|
}
|
|
36
|
-
throw new Error(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
throw new Error(`[LLMFactory] Unknown provider "${llmConfig.provider}"`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static getValidator(provider: LLMProvider): IProviderValidator | null {
|
|
42
|
+
const providerClass = this.getProviderClass(provider);
|
|
43
|
+
return providerClass && providerClass.getValidator ? providerClass.getValidator() : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static getHealthChecker(provider: LLMProvider): IProviderHealthChecker | null {
|
|
47
|
+
const providerClass = this.getProviderClass(provider);
|
|
48
|
+
return providerClass && providerClass.getHealthChecker ? providerClass.getHealthChecker() : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private static getProviderClass(provider: string): LLMProviderStatic | null {
|
|
52
|
+
switch (provider) {
|
|
53
|
+
case 'openai': return OpenAIProvider;
|
|
54
|
+
case 'anthropic': return AnthropicProvider;
|
|
55
|
+
case 'ollama': return OllamaProvider;
|
|
56
|
+
case 'gemini': return GeminiProvider;
|
|
57
|
+
case 'rest':
|
|
58
|
+
case 'universal_rest':
|
|
59
|
+
case 'custom': return UniversalLLMAdapter as unknown as LLMProviderStatic;
|
|
60
|
+
default: return null;
|
|
40
61
|
}
|
|
41
62
|
}
|
|
42
63
|
|
|
43
64
|
/**
|
|
44
65
|
* Creates a dedicated embedding-only provider.
|
|
45
|
-
* Useful when the LLM provider (e.g. Anthropic) doesn't support embeddings.
|
|
46
66
|
*/
|
|
47
67
|
static createEmbeddingProvider(embeddingConfig: EmbeddingConfig): ILLMProvider {
|
|
48
68
|
const fakeLLMConfig: LLMConfig = {
|
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Anthropic (Claude) LLM Provider
|
|
3
|
-
*
|
|
4
|
-
* Handles chat completion via Anthropic's Messages API.
|
|
5
|
-
* Note: Anthropic does NOT provide embedding models — use OpenAIProvider
|
|
6
|
-
* or OllamaProvider for embedding when pairing with this provider.
|
|
7
|
-
*
|
|
8
|
-
* Required LLMConfig fields:
|
|
9
|
-
* - apiKey: string – Anthropic API key
|
|
10
|
-
* - model: string – e.g. "claude-3-5-sonnet-20241022"
|
|
11
3
|
*/
|
|
12
4
|
|
|
13
5
|
import Anthropic from '@anthropic-ai/sdk';
|
|
14
6
|
import { ILLMProvider, ChatMessage, ChatOptions, EmbedOptions } from '../ILLMProvider';
|
|
15
7
|
import { LLMConfig, EmbeddingConfig } from '../../config/RagConfig';
|
|
8
|
+
import { IProviderValidator, IProviderHealthChecker, HealthCheckResult } from '../../core/ProviderInterfaces';
|
|
9
|
+
import { ValidationError } from '../../core/ConfigValidator';
|
|
16
10
|
|
|
17
11
|
export class AnthropicProvider implements ILLMProvider {
|
|
18
12
|
private readonly client: Anthropic;
|
|
@@ -26,6 +20,59 @@ export class AnthropicProvider implements ILLMProvider {
|
|
|
26
20
|
this.embeddingConfig = embeddingConfig;
|
|
27
21
|
}
|
|
28
22
|
|
|
23
|
+
static getValidator(): IProviderValidator {
|
|
24
|
+
return {
|
|
25
|
+
validate(config: Record<string, unknown>): ValidationError[] {
|
|
26
|
+
const errors: ValidationError[] = [];
|
|
27
|
+
if (!config.apiKey) {
|
|
28
|
+
errors.push({
|
|
29
|
+
field: 'llm.apiKey',
|
|
30
|
+
message: 'Anthropic API key is required',
|
|
31
|
+
suggestion: 'Set ANTHROPIC_API_KEY environment variable',
|
|
32
|
+
severity: 'error',
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (!config.model) {
|
|
36
|
+
errors.push({
|
|
37
|
+
field: 'llm.model',
|
|
38
|
+
message: 'Anthropic model name is required',
|
|
39
|
+
suggestion: 'e.g., "claude-3-5-sonnet-20241022"',
|
|
40
|
+
severity: 'error',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return errors;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static getHealthChecker(): IProviderHealthChecker {
|
|
49
|
+
return {
|
|
50
|
+
async check(config: Record<string, unknown>): Promise<HealthCheckResult> {
|
|
51
|
+
const timestamp = Date.now();
|
|
52
|
+
const apiKey = config.apiKey as string;
|
|
53
|
+
const modelName = config.model as string;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const { default: Anthropic } = await import('@anthropic-ai/sdk');
|
|
57
|
+
const client = new Anthropic({ apiKey });
|
|
58
|
+
await client.messages.create({
|
|
59
|
+
model: modelName,
|
|
60
|
+
max_tokens: 10,
|
|
61
|
+
messages: [{ role: 'user', content: 'ping' }],
|
|
62
|
+
});
|
|
63
|
+
return { healthy: true, provider: 'anthropic', capabilities: { model: modelName }, timestamp };
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return {
|
|
66
|
+
healthy: false,
|
|
67
|
+
provider: 'anthropic',
|
|
68
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
69
|
+
timestamp,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
29
76
|
async chat(messages: ChatMessage[], context: string, options?: ChatOptions): Promise<string> {
|
|
30
77
|
const systemPrompt =
|
|
31
78
|
this.llmConfig.systemPrompt ??
|
|
@@ -35,7 +82,6 @@ export class AnthropicProvider implements ILLMProvider {
|
|
|
35
82
|
? systemPrompt.replace('{{context}}', context)
|
|
36
83
|
: `${systemPrompt}\n\nContext:\n${context}`;
|
|
37
84
|
|
|
38
|
-
// Anthropic requires alternating user/assistant messages
|
|
39
85
|
const anthropicMessages: Anthropic.MessageParam[] = messages.map((m) => ({
|
|
40
86
|
role: m.role === 'assistant' ? 'assistant' : 'user',
|
|
41
87
|
content: m.content,
|
|
@@ -52,11 +98,6 @@ export class AnthropicProvider implements ILLMProvider {
|
|
|
52
98
|
return block.type === 'text' ? block.text : '';
|
|
53
99
|
}
|
|
54
100
|
|
|
55
|
-
/**
|
|
56
|
-
* Anthropic does not offer an embedding API.
|
|
57
|
-
* This method throws with a clear error so developers know to configure
|
|
58
|
-
* a separate embedding provider (OpenAI or Ollama).
|
|
59
|
-
*/
|
|
60
101
|
async embed(text: string, options?: EmbedOptions): Promise<number[]> {
|
|
61
102
|
void text;
|
|
62
103
|
void options;
|
|
@@ -76,7 +117,6 @@ export class AnthropicProvider implements ILLMProvider {
|
|
|
76
117
|
|
|
77
118
|
async ping(): Promise<boolean> {
|
|
78
119
|
try {
|
|
79
|
-
// Lightweight model list call to verify connectivity
|
|
80
120
|
await this.client.models.list();
|
|
81
121
|
return true;
|
|
82
122
|
} catch (err) {
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
import { GoogleGenAI } from '@google/genai';
|
|
18
18
|
import { ILLMProvider, ChatMessage, ChatOptions, EmbedOptions } from '../ILLMProvider';
|
|
19
19
|
import { LLMConfig, EmbeddingConfig } from '../../config/RagConfig';
|
|
20
|
+
import { IProviderValidator, IProviderHealthChecker, HealthCheckResult } from '../../core/ProviderInterfaces';
|
|
21
|
+
import { ValidationError } from '../../core/ConfigValidator';
|
|
20
22
|
|
|
21
23
|
export class GeminiProvider implements ILLMProvider {
|
|
22
24
|
private readonly client: GoogleGenAI;
|
|
@@ -41,6 +43,55 @@ export class GeminiProvider implements ILLMProvider {
|
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
static getValidator(): IProviderValidator {
|
|
47
|
+
return {
|
|
48
|
+
validate(config: Record<string, unknown>): ValidationError[] {
|
|
49
|
+
const errors: ValidationError[] = [];
|
|
50
|
+
if (!config.apiKey && !process.env.GOOGLE_GENAI_API_KEY) {
|
|
51
|
+
errors.push({
|
|
52
|
+
field: 'llm.apiKey',
|
|
53
|
+
message: 'Gemini API key is required',
|
|
54
|
+
suggestion: 'Set GOOGLE_GENAI_API_KEY environment variable or provide in config',
|
|
55
|
+
severity: 'error'
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (!config.model) {
|
|
59
|
+
errors.push({ field: 'llm.model', message: 'Gemini model name is required', severity: 'error' });
|
|
60
|
+
}
|
|
61
|
+
return errors;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static getHealthChecker(): IProviderHealthChecker {
|
|
67
|
+
return {
|
|
68
|
+
async check(config: Record<string, unknown>): Promise<HealthCheckResult> {
|
|
69
|
+
const timestamp = Date.now();
|
|
70
|
+
const apiKey = (config.apiKey as string) || process.env.GOOGLE_GENAI_API_KEY || '';
|
|
71
|
+
const modelName = config.model as string;
|
|
72
|
+
try {
|
|
73
|
+
const { GoogleGenAI } = await import('@google/genai');
|
|
74
|
+
const genAI = new GoogleGenAI({ apiKey });
|
|
75
|
+
// Verify model availability
|
|
76
|
+
await genAI.models.get({ model: modelName });
|
|
77
|
+
return {
|
|
78
|
+
healthy: true,
|
|
79
|
+
provider: 'gemini',
|
|
80
|
+
capabilities: { model: modelName },
|
|
81
|
+
timestamp,
|
|
82
|
+
};
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return {
|
|
85
|
+
healthy: false,
|
|
86
|
+
provider: 'gemini',
|
|
87
|
+
error: error instanceof Error ? error.message : String(error),
|
|
88
|
+
timestamp,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
44
95
|
private sanitizeModel(model: string): string {
|
|
45
96
|
if (!model) return model;
|
|
46
97
|
// Strip :latest suffix common in Ollama but invalid in Google SDK
|