@retrivora-ai/rag-engine 2.2.4 → 2.2.5
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/dist/handlers/index.d.mts +1 -1
- package/dist/handlers/index.d.ts +1 -1
- package/dist/handlers/index.js +147 -843
- package/dist/handlers/index.mjs +147 -843
- package/dist/{index-C6ehmP0b.d.ts → index-B5TTZWkx.d.ts} +6 -6
- package/dist/{index-DFc_Ll9z.d.mts → index-DvbtNz7m.d.mts} +6 -6
- package/dist/index.js +3 -14
- package/dist/index.mjs +3 -14
- package/dist/server.d.mts +3 -4
- package/dist/server.d.ts +3 -4
- package/dist/server.js +147 -843
- package/dist/server.mjs +147 -843
- package/package.json +3 -1
- package/src/config/UniversalProfiles.ts +1 -2
- package/src/config/serverConfig.ts +5 -3
- package/src/core/ConfigFetcher.ts +1 -1
- package/src/core/Pipeline.ts +24 -27
- package/src/core/VectorPlugin.ts +2 -2
- package/src/handlers/index.ts +11 -14
- package/src/llm/providers/UniversalLLMAdapter.ts +53 -104
- package/src/utils/SchemaMapper.ts +107 -62
- package/src/utils/UITransformer.ts +22 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.5",
|
|
4
4
|
"description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
|
|
5
5
|
"author": "Abhinav Alkuchi",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
"import": "./dist/index.mjs"
|
|
38
38
|
},
|
|
39
39
|
"./style.css": "./dist/index.css",
|
|
40
|
+
"./index.css": "./dist/index.css",
|
|
41
|
+
"./styles.css": "./dist/index.css",
|
|
40
42
|
"./handlers": {
|
|
41
43
|
"types": "./dist/handlers/index.d.ts",
|
|
42
44
|
"require": "./dist/handlers/index.js",
|
|
@@ -14,8 +14,7 @@ const OPENAI_BASE = {
|
|
|
14
14
|
export const LLM_PROFILES = {
|
|
15
15
|
'openai-compatible': OPENAI_BASE,
|
|
16
16
|
'litellm': {
|
|
17
|
-
...OPENAI_BASE,
|
|
18
|
-
chatPayloadTemplate: '{"model":"{{model}}","messages":{{messages}},"max_tokens":{{maxTokens}},"temperature":{{temperature}},"stream":false}'
|
|
17
|
+
...OPENAI_BASE,
|
|
19
18
|
},
|
|
20
19
|
'anthropic-claude': {
|
|
21
20
|
chatPath: '/v1/messages',
|
|
@@ -187,8 +187,10 @@ export function getEnvConfig(env: Record<string, string | undefined> = process.e
|
|
|
187
187
|
|
|
188
188
|
// Default gateway: retrivora.com/api/v1 acts as a secure proxy that validates the license key
|
|
189
189
|
// and forwards to llm.retrivora.com with LITELLM_MASTER_KEY (which end-users never need to know).
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
const defaultGatewayUrl =
|
|
191
|
+
readString(env, 'LITELLM_BASE_URL') ??
|
|
192
|
+
readString(env, 'LLM_BASE_URL') ??
|
|
193
|
+
'https://www.retrivora.com/api/v1';
|
|
192
194
|
|
|
193
195
|
const defaultLlmProvider = isFreeTier ? 'universal_rest' : 'universal_rest';
|
|
194
196
|
const llmProvider = (readEnum(env, 'LLM_PROVIDER', defaultLlmProvider, LLM_PROVIDERS) ?? base?.llm?.provider ?? defaultLlmProvider) as LLMProvider;
|
|
@@ -277,7 +279,7 @@ export function getEnvConfig(env: Record<string, string | undefined> = process.e
|
|
|
277
279
|
},
|
|
278
280
|
telemetry: {
|
|
279
281
|
enabled: telemetryEnabled,
|
|
280
|
-
url: readString(env, 'TELEMETRY_URL') ?? readString(env, 'NEXT_PUBLIC_TELEMETRY_URL') ?? base?.telemetry?.url ??
|
|
282
|
+
url: readString(env, 'TELEMETRY_URL') ?? readString(env, 'NEXT_PUBLIC_TELEMETRY_URL') ?? base?.telemetry?.url ?? 'https://www.retrivora.com/api/telemetry',
|
|
281
283
|
},
|
|
282
284
|
// Optional graph DB — driven by GRAPH_DB_PROVIDER env var
|
|
283
285
|
...(readString(env, 'GRAPH_DB_PROVIDER') ? {
|
|
@@ -57,8 +57,8 @@ export class ConfigFetcher {
|
|
|
57
57
|
process.env.NEXT_PUBLIC_RETRIVORA_CONTROL_PLANE_URL,
|
|
58
58
|
'https://www.retrivora.com',
|
|
59
59
|
'https://retrivora.com',
|
|
60
|
-
'http://localhost:3001',
|
|
61
60
|
'http://localhost:3000',
|
|
61
|
+
'http://localhost:3001',
|
|
62
62
|
].filter(Boolean) as string[];
|
|
63
63
|
|
|
64
64
|
for (const baseUrl of controlPlaneUrls) {
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -586,16 +586,16 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
586
586
|
? await this.vectorDB.query(queryVector, retrievalLimit, ns, filter)
|
|
587
587
|
: []).filter((s): s is VectorMatch => Boolean(s && typeof s === 'object'));
|
|
588
588
|
|
|
589
|
-
console.log('[Pipeline] Raw vectorDB.query response:', {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
});
|
|
589
|
+
// console.log('[Pipeline] Raw vectorDB.query response:', {
|
|
590
|
+
// rawCount: rawSources.length,
|
|
591
|
+
// sample: rawSources.slice(0, 2).map((s) => ({
|
|
592
|
+
// id: s?.id,
|
|
593
|
+
// score: s?.score,
|
|
594
|
+
// contentType: typeof s?.content,
|
|
595
|
+
// contentSnippet: String(s?.content ?? '').substring(0, 150),
|
|
596
|
+
// metadataKeys: Object.keys(s?.metadata || {}),
|
|
597
|
+
// })),
|
|
598
|
+
// });
|
|
599
599
|
|
|
600
600
|
const retrieveEnd = performance.now();
|
|
601
601
|
const embedMs = retrieveEnd - embedStart;
|
|
@@ -628,18 +628,18 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
628
628
|
.filter((s): s is VectorMatch => Boolean(s && typeof s === 'object'))
|
|
629
629
|
.sort((a, b) => (b?.score ?? 0) - (a?.score ?? 0));
|
|
630
630
|
|
|
631
|
-
console.log('[Pipeline] Final sources for prompt & UI:', {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
});
|
|
631
|
+
// console.log('[Pipeline] Final sources for prompt & UI:', {
|
|
632
|
+
// count: sources.length,
|
|
633
|
+
// totalRawCount: rawSources.length,
|
|
634
|
+
// sources: sources.slice(0, 10).map((s, idx) => ({
|
|
635
|
+
// rank: idx + 1,
|
|
636
|
+
// id: s?.id,
|
|
637
|
+
// score: s?.score,
|
|
638
|
+
// contentType: typeof s?.content,
|
|
639
|
+
// contentSnippet: String(s?.content ?? '').substring(0, 150),
|
|
640
|
+
// metadata: s?.metadata,
|
|
641
|
+
// })),
|
|
642
|
+
// });
|
|
643
643
|
|
|
644
644
|
if (graphData && graphData.nodes.length > 0) {
|
|
645
645
|
const graphContext = graphData.nodes.map(n =>
|
|
@@ -912,14 +912,11 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
912
912
|
// Asynchronously trigger telemetry logging without blocking response yield
|
|
913
913
|
const isTelemetryActive = this.config.telemetry?.enabled ?? Boolean(this.config.licenseKey);
|
|
914
914
|
if (isTelemetryActive) {
|
|
915
|
-
const defaultUrl =
|
|
916
|
-
? 'http://localhost:3001/api/telemetry'
|
|
917
|
-
: 'https://retrivora.com/api/telemetry';
|
|
918
|
-
|
|
915
|
+
const defaultUrl = 'https://www.retrivora.com/api/telemetry';
|
|
919
916
|
const telemetryUrl = this.config.telemetry?.url || process.env.TELEMETRY_URL || process.env.NEXT_PUBLIC_TELEMETRY_URL || defaultUrl;
|
|
920
917
|
const absoluteUrl = telemetryUrl.startsWith('http')
|
|
921
918
|
? telemetryUrl
|
|
922
|
-
:
|
|
919
|
+
: 'https://www.retrivora.com' + (telemetryUrl.startsWith('/') ? telemetryUrl : '/' + telemetryUrl);
|
|
923
920
|
|
|
924
921
|
// Start background process for telemetry, awaiting hallucination scoring if it was backgrounded
|
|
925
922
|
(async () => {
|
package/src/core/VectorPlugin.ts
CHANGED
package/src/handlers/index.ts
CHANGED
|
@@ -166,13 +166,10 @@ function reportTelemetry(
|
|
|
166
166
|
const licenseKey = config.licenseKey || process.env.RAG_LICENSE_KEY || process.env.RETRIVORA_LICENSE_KEY || process.env.NEXT_PUBLIC_RETRIVORA_LICENSE_KEY;
|
|
167
167
|
const telemetryConfig = config.telemetry;
|
|
168
168
|
|
|
169
|
-
const
|
|
170
|
-
const defaultUrl =
|
|
171
|
-
? 'http://localhost:3001/api/telemetry'
|
|
172
|
-
: 'https://www.retrivora.com/api/telemetry';
|
|
169
|
+
const host = req.headers.get('host') || 'localhost';
|
|
170
|
+
const defaultUrl = 'https://www.retrivora.com/api/telemetry';
|
|
173
171
|
|
|
174
172
|
const telemetryUrl = telemetryConfig?.url || process.env.TELEMETRY_URL || process.env.NEXT_PUBLIC_TELEMETRY_URL || defaultUrl;
|
|
175
|
-
const host = req.headers.get('host') || 'localhost';
|
|
176
173
|
const userAgent = req.headers.get('user-agent') || `Retrivora-SDK/${SDK_VERSION}`;
|
|
177
174
|
|
|
178
175
|
let absoluteUrl = telemetryUrl;
|
|
@@ -285,7 +282,7 @@ export function createChatHandler(
|
|
|
285
282
|
const storage = new DatabaseStorage(plugin.getConfig());
|
|
286
283
|
const onAuthorize = options?.onAuthorize;
|
|
287
284
|
|
|
288
|
-
return async function POST(req:
|
|
285
|
+
return async function POST(req: any, context?: any) {
|
|
289
286
|
const authResult = await checkAuth(req, onAuthorize);
|
|
290
287
|
if (authResult) return authResult as any;
|
|
291
288
|
|
|
@@ -361,7 +358,7 @@ export function createStreamHandler(
|
|
|
361
358
|
const storage = new DatabaseStorage(plugin.getConfig());
|
|
362
359
|
const onAuthorize = options?.onAuthorize;
|
|
363
360
|
|
|
364
|
-
return async function POST(req:
|
|
361
|
+
return async function POST(req: any, context?: any) {
|
|
365
362
|
const authResult = await checkAuth(req, onAuthorize);
|
|
366
363
|
if (authResult) return authResult as any;
|
|
367
364
|
|
|
@@ -532,7 +529,7 @@ export function createIngestHandler(
|
|
|
532
529
|
const plugin = getOrCreatePlugin(configOrPlugin);
|
|
533
530
|
const onAuthorize = options?.onAuthorize;
|
|
534
531
|
|
|
535
|
-
return async function POST(req:
|
|
532
|
+
return async function POST(req: any, context?: any) {
|
|
536
533
|
const authResult = await checkAuth(req, onAuthorize);
|
|
537
534
|
if (authResult) return authResult as any;
|
|
538
535
|
|
|
@@ -599,7 +596,7 @@ export function createUploadHandler(
|
|
|
599
596
|
const plugin = getOrCreatePlugin(configOrPlugin);
|
|
600
597
|
const onAuthorize = options?.onAuthorize;
|
|
601
598
|
|
|
602
|
-
return async function POST(req:
|
|
599
|
+
return async function POST(req: any, context?: any) {
|
|
603
600
|
const authResult = await checkAuth(req, onAuthorize);
|
|
604
601
|
if (authResult) return authResult as any;
|
|
605
602
|
|
|
@@ -926,7 +923,7 @@ export function createRagHandler(
|
|
|
926
923
|
const feedbackHandler = createFeedbackHandler(plugin, { onAuthorize });
|
|
927
924
|
const sessionsHandler = createSessionsHandler(plugin, { onAuthorize });
|
|
928
925
|
|
|
929
|
-
async function routePostRequest(req:
|
|
926
|
+
async function routePostRequest(req: any, segment: string) {
|
|
930
927
|
switch (segment) {
|
|
931
928
|
case 'chat':
|
|
932
929
|
return streamHandler(req);
|
|
@@ -948,7 +945,7 @@ export function createRagHandler(
|
|
|
948
945
|
}
|
|
949
946
|
}
|
|
950
947
|
|
|
951
|
-
async function routeGetRequest(req:
|
|
948
|
+
async function routeGetRequest(req: any, segment: string) {
|
|
952
949
|
switch (segment) {
|
|
953
950
|
case 'health':
|
|
954
951
|
return healthHandler(req);
|
|
@@ -965,7 +962,7 @@ export function createRagHandler(
|
|
|
965
962
|
}
|
|
966
963
|
}
|
|
967
964
|
|
|
968
|
-
async function getSegment(req:
|
|
965
|
+
async function getSegment(req: any, context: any): Promise<string> {
|
|
969
966
|
// 1. If content-type is multipart/form-data, it MUST be an upload request!
|
|
970
967
|
const contentType = req.headers.get('content-type') || '';
|
|
971
968
|
if (contentType.includes('multipart/form-data')) {
|
|
@@ -995,7 +992,7 @@ export function createRagHandler(
|
|
|
995
992
|
}
|
|
996
993
|
|
|
997
994
|
return {
|
|
998
|
-
GET: async (req:
|
|
995
|
+
GET: async (req: any, context?: any) => {
|
|
999
996
|
try {
|
|
1000
997
|
const segment = await getSegment(req, context);
|
|
1001
998
|
return await routeGetRequest(req, segment);
|
|
@@ -1004,7 +1001,7 @@ export function createRagHandler(
|
|
|
1004
1001
|
return NextResponse.json({ error: msg }, { status: 500 });
|
|
1005
1002
|
}
|
|
1006
1003
|
},
|
|
1007
|
-
POST: async (req:
|
|
1004
|
+
POST: async (req: any, context?: any) => {
|
|
1008
1005
|
try {
|
|
1009
1006
|
const segment = await getSegment(req, context);
|
|
1010
1007
|
return await routePostRequest(req, segment);
|
|
@@ -43,10 +43,7 @@ export class UniversalLLMAdapter implements ILLMProvider {
|
|
|
43
43
|
constructor(config: LLMConfig | EmbeddingConfig) {
|
|
44
44
|
this.model = config.model;
|
|
45
45
|
|
|
46
|
-
// Type narrow to extract LLM-specific fields safely
|
|
47
46
|
const llmConfig = config as LLMConfig;
|
|
48
|
-
|
|
49
|
-
// Merge profile defaults if specified in options
|
|
50
47
|
const options = (llmConfig.options as Record<string, unknown>) ?? {};
|
|
51
48
|
let profile: Record<string, unknown> = {};
|
|
52
49
|
|
|
@@ -112,7 +109,7 @@ export class UniversalLLMAdapter implements ILLMProvider {
|
|
|
112
109
|
};
|
|
113
110
|
}
|
|
114
111
|
|
|
115
|
-
//
|
|
112
|
+
// Tier 1: Remote HTTP POST to Retrivora Cloud Proxy (e.g. https://www.retrivora.com/api/v1/chat/completions)
|
|
116
113
|
try {
|
|
117
114
|
const { data } = await this.http.post(path, payload);
|
|
118
115
|
const extractPath = this.opts.responseExtractPath ?? 'choices[0].message.content';
|
|
@@ -121,20 +118,9 @@ export class UniversalLLMAdapter implements ILLMProvider {
|
|
|
121
118
|
return String(result);
|
|
122
119
|
}
|
|
123
120
|
} catch (httpErr: any) {
|
|
124
|
-
//
|
|
125
|
-
|
|
121
|
+
// Tier 2: In-process fallback (only if HTTP call fails and server registered dispatch on globalThis)
|
|
126
122
|
const _g = globalThis as any;
|
|
127
|
-
|
|
128
|
-
if (typeof dispatch !== 'function') {
|
|
129
|
-
try {
|
|
130
|
-
const gateway = await import('../../../../../services/llm-gateway/router');
|
|
131
|
-
if (gateway?.dispatchChatCompletion) {
|
|
132
|
-
_g.__retrivoraDispatchChat = gateway.dispatchChatCompletion;
|
|
133
|
-
_g.__retrivoraDispatchEmbedding = gateway.dispatchEmbedding;
|
|
134
|
-
dispatch = gateway.dispatchChatCompletion;
|
|
135
|
-
}
|
|
136
|
-
} catch { /* proceed */ }
|
|
137
|
-
}
|
|
123
|
+
const dispatch = _g.__retrivoraDispatchChat;
|
|
138
124
|
|
|
139
125
|
if (typeof dispatch === 'function') {
|
|
140
126
|
const res = await dispatch({
|
|
@@ -153,7 +139,6 @@ export class UniversalLLMAdapter implements ILLMProvider {
|
|
|
153
139
|
}
|
|
154
140
|
|
|
155
141
|
const { data } = await this.http.post(path, payload);
|
|
156
|
-
|
|
157
142
|
const extractPath = this.opts.responseExtractPath ?? 'choices[0].message.content';
|
|
158
143
|
const result = resolvePath(data, extractPath) ?? extractContent(data);
|
|
159
144
|
|
|
@@ -167,12 +152,10 @@ export class UniversalLLMAdapter implements ILLMProvider {
|
|
|
167
152
|
/**
|
|
168
153
|
* Streaming chat using native fetch + ReadableStream.
|
|
169
154
|
* Parses OpenAI-compatible SSE frames: `data: {...}\n\n`
|
|
170
|
-
* Works with vLLM, LMStudio, Together AI, Fireworks, and any OpenAI-compatible API.
|
|
171
155
|
*/
|
|
172
156
|
async *chatStream(messages: ChatMessage[], context?: string): AsyncIterable<string> {
|
|
173
157
|
const path = this.opts.chatPath ?? '/chat/completions';
|
|
174
158
|
const url = `${this.baseUrl.replace(/\/$/, '')}${path}`;
|
|
175
|
-
// Delta extract path — same root as chat but with delta instead of message
|
|
176
159
|
const extractPath = (this.opts.responseExtractPath ?? 'choices[0].message.content')
|
|
177
160
|
.replace('message.content', 'delta.content');
|
|
178
161
|
|
|
@@ -196,7 +179,6 @@ export class UniversalLLMAdapter implements ILLMProvider {
|
|
|
196
179
|
maxTokens: this.maxTokens,
|
|
197
180
|
temperature: this.temperature,
|
|
198
181
|
});
|
|
199
|
-
// Inject stream: true into the payload if it's an object
|
|
200
182
|
if (typeof payload === 'object' && payload !== null) {
|
|
201
183
|
(payload as Record<string, unknown>).stream = true;
|
|
202
184
|
}
|
|
@@ -210,22 +192,40 @@ export class UniversalLLMAdapter implements ILLMProvider {
|
|
|
210
192
|
};
|
|
211
193
|
}
|
|
212
194
|
|
|
213
|
-
const isSelfHost = this.baseUrl.includes('retrivora.com') || this.baseUrl.includes('localhost');
|
|
214
195
|
let streamBody: ReadableStream<Uint8Array> | null = null;
|
|
215
196
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
197
|
+
// ── Tier 1: HTTP Stream Request to Retrivora Gateway Proxy ─────────────────
|
|
198
|
+
try {
|
|
199
|
+
const response = await fetch(url, {
|
|
200
|
+
method: 'POST',
|
|
201
|
+
headers: this.resolvedHeaders,
|
|
202
|
+
body: typeof payload === 'string' ? payload : JSON.stringify(payload),
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
if (response.ok) {
|
|
206
|
+
const contentType = response.headers.get('content-type') || '';
|
|
207
|
+
if (contentType.includes('application/json')) {
|
|
208
|
+
const json = await response.json();
|
|
209
|
+
const text = (resolvePath(json, extractPath) as string | undefined) ?? extractContent(json);
|
|
210
|
+
if (text && typeof text === 'string') {
|
|
211
|
+
yield text;
|
|
212
|
+
return;
|
|
226
213
|
}
|
|
227
|
-
}
|
|
214
|
+
} else if (response.body) {
|
|
215
|
+
streamBody = response.body;
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
219
|
+
console.warn(`[UniversalLLMAdapter] Remote HTTP stream returned ${response.status}: ${errorText}. Attempting in-process fallback...`);
|
|
228
220
|
}
|
|
221
|
+
} catch (fetchErr: any) {
|
|
222
|
+
console.warn(`[UniversalLLMAdapter] Remote HTTP stream fetch warning: ${fetchErr?.message}. Attempting in-process fallback...`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ── Tier 2: In-process Dispatch Fallback (if remote HTTP fails) ────────────
|
|
226
|
+
if (!streamBody) {
|
|
227
|
+
const _g = globalThis as any;
|
|
228
|
+
const dispatch = _g.__retrivoraDispatchChat;
|
|
229
229
|
|
|
230
230
|
if (typeof dispatch === 'function') {
|
|
231
231
|
try {
|
|
@@ -245,32 +245,15 @@ export class UniversalLLMAdapter implements ILLMProvider {
|
|
|
245
245
|
yield content;
|
|
246
246
|
return;
|
|
247
247
|
}
|
|
248
|
-
throw new Error(`[UniversalLLMAdapter] In-process dispatch stream returned empty response for model: ${this.model}`);
|
|
249
248
|
}
|
|
250
249
|
} catch (dispatchErr) {
|
|
251
|
-
|
|
250
|
+
console.warn('[UniversalLLMAdapter] In-process dispatch error:', dispatchErr);
|
|
252
251
|
}
|
|
253
|
-
} else {
|
|
254
|
-
throw new Error(`[UniversalLLMAdapter] In-process gateway dispatch not registered. Direct self-referential HTTP calls to ${this.baseUrl} are disabled on Vercel to prevent HTTP 508 Loop Detected.`);
|
|
255
252
|
}
|
|
256
253
|
}
|
|
257
254
|
|
|
258
255
|
if (!streamBody) {
|
|
259
|
-
|
|
260
|
-
method: 'POST',
|
|
261
|
-
headers: this.resolvedHeaders,
|
|
262
|
-
body: JSON.stringify(payload),
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
if (!response.ok) {
|
|
266
|
-
const errorText = await response.text().catch(() => response.statusText);
|
|
267
|
-
throw new Error(`[UniversalLLMAdapter] Streaming request failed (${response.status}): ${errorText}`);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (!response.body) {
|
|
271
|
-
throw new Error('[UniversalLLMAdapter] Response body is null — server did not send a streaming response.');
|
|
272
|
-
}
|
|
273
|
-
streamBody = response.body;
|
|
256
|
+
throw new Error(`[UniversalLLMAdapter] Streaming request failed. Ensure RETRIVORA_LICENSE_KEY is valid or LLM service is available.`);
|
|
274
257
|
}
|
|
275
258
|
|
|
276
259
|
const reader = streamBody.getReader();
|
|
@@ -296,12 +279,11 @@ export class UniversalLLMAdapter implements ILLMProvider {
|
|
|
296
279
|
const text = (resolvePath(json, extractPath) as string | undefined) ?? extractContent(json);
|
|
297
280
|
if (text && typeof text === 'string') yield text;
|
|
298
281
|
} catch {
|
|
299
|
-
//
|
|
282
|
+
// Skip malformed SSE lines
|
|
300
283
|
}
|
|
301
284
|
}
|
|
302
285
|
}
|
|
303
286
|
|
|
304
|
-
// Flush remaining buffer
|
|
305
287
|
if (buffer.trim() && buffer.trim() !== 'data: [DONE]') {
|
|
306
288
|
const jsonStr = buffer.replace(/^data:\s*/, '').trim();
|
|
307
289
|
try {
|
|
@@ -317,85 +299,52 @@ export class UniversalLLMAdapter implements ILLMProvider {
|
|
|
317
299
|
|
|
318
300
|
async embed(text: string): Promise<number[]> {
|
|
319
301
|
const path = this.opts.embedPath ?? '/embeddings';
|
|
302
|
+
const payload = this.opts.embedPayloadTemplate
|
|
303
|
+
? buildPayload(this.opts.embedPayloadTemplate, { input: text, model: this.model })
|
|
304
|
+
: { input: text, model: this.model };
|
|
320
305
|
|
|
321
|
-
let payload: unknown;
|
|
322
|
-
if (this.opts.embedPayloadTemplate) {
|
|
323
|
-
payload = buildPayload(this.opts.embedPayloadTemplate, {
|
|
324
|
-
model: this.model,
|
|
325
|
-
input: text,
|
|
326
|
-
});
|
|
327
|
-
} else {
|
|
328
|
-
payload = {
|
|
329
|
-
model: this.model,
|
|
330
|
-
input: text,
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Try direct HTTP POST request to llm.retrivora.com first
|
|
335
306
|
try {
|
|
336
307
|
const { data } = await this.http.post(path, payload);
|
|
337
308
|
const extractPath = this.opts.embedExtractPath ?? 'data[0].embedding';
|
|
338
|
-
const vector = resolvePath(data, extractPath);
|
|
339
|
-
if (Array.isArray(vector))
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
} catch (httpErr: any) {
|
|
343
|
-
console.warn(`[UniversalLLMAdapter] Direct HTTP embedding POST to ${this.baseUrl}${path} failed (${httpErr.message}). Attempting in-process gateway dispatch fallback...`);
|
|
344
|
-
|
|
309
|
+
const vector = resolvePath(data, extractPath) as number[] | undefined;
|
|
310
|
+
if (Array.isArray(vector)) return vector;
|
|
311
|
+
} catch {
|
|
312
|
+
// In-process fallback for embeddings
|
|
345
313
|
const _g = globalThis as any;
|
|
346
|
-
|
|
347
|
-
if (typeof dispatch !== 'function') {
|
|
348
|
-
try {
|
|
349
|
-
const gateway = await import('../../../../../services/llm-gateway/router');
|
|
350
|
-
if (gateway?.dispatchEmbedding) {
|
|
351
|
-
_g.__retrivoraDispatchChat = gateway.dispatchChatCompletion;
|
|
352
|
-
_g.__retrivoraDispatchEmbedding = gateway.dispatchEmbedding;
|
|
353
|
-
dispatch = gateway.dispatchEmbedding;
|
|
354
|
-
}
|
|
355
|
-
} catch { /* proceed */ }
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (typeof dispatch === 'function') {
|
|
359
|
-
const res = await dispatch({
|
|
360
|
-
model: this.model,
|
|
361
|
-
input: text,
|
|
362
|
-
}, this.apiKey);
|
|
314
|
+
const dispatchEmbed = _g.__retrivoraDispatchEmbedding;
|
|
363
315
|
|
|
316
|
+
if (typeof dispatchEmbed === 'function') {
|
|
317
|
+
const res = await dispatchEmbed({ input: text, model: this.model }, this.apiKey);
|
|
364
318
|
if (res?.data?.[0]?.embedding) {
|
|
365
319
|
return res.data[0].embedding;
|
|
366
320
|
}
|
|
367
321
|
}
|
|
368
|
-
throw httpErr;
|
|
369
322
|
}
|
|
370
323
|
|
|
371
324
|
const { data } = await this.http.post(path, payload);
|
|
372
|
-
|
|
373
325
|
const extractPath = this.opts.embedExtractPath ?? 'data[0].embedding';
|
|
374
|
-
const vector = resolvePath(data, extractPath);
|
|
326
|
+
const vector = resolvePath(data, extractPath) as number[] | undefined;
|
|
375
327
|
|
|
376
328
|
if (!Array.isArray(vector)) {
|
|
377
|
-
throw new Error(`[UniversalLLMAdapter]
|
|
329
|
+
throw new Error(`[UniversalLLMAdapter] Could not extract embedding vector from path '${extractPath}' in response.`);
|
|
378
330
|
}
|
|
379
331
|
|
|
380
332
|
return vector;
|
|
381
333
|
}
|
|
382
334
|
|
|
383
335
|
async batchEmbed(texts: string[]): Promise<number[][]> {
|
|
384
|
-
const
|
|
336
|
+
const results: number[][] = [];
|
|
385
337
|
for (const text of texts) {
|
|
386
|
-
|
|
338
|
+
results.push(await this.embed(text));
|
|
387
339
|
}
|
|
388
|
-
return
|
|
340
|
+
return results;
|
|
389
341
|
}
|
|
390
342
|
|
|
391
343
|
async ping(): Promise<boolean> {
|
|
392
344
|
try {
|
|
393
|
-
|
|
394
|
-
await this.http.get(this.opts.pingPath);
|
|
395
|
-
}
|
|
345
|
+
await this.embed('ping');
|
|
396
346
|
return true;
|
|
397
|
-
} catch
|
|
398
|
-
console.error('[UniversalLLMAdapter] Ping failed:', err);
|
|
347
|
+
} catch {
|
|
399
348
|
return false;
|
|
400
349
|
}
|
|
401
350
|
}
|