@retrivora-ai/rag-engine 1.9.6 → 1.9.8

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.
Files changed (62) hide show
  1. package/README.md +130 -113
  2. package/dist/{ILLMProvider-DNhyOYoK.d.mts → ILLMProvider-B8ROITYK.d.mts} +59 -8
  3. package/dist/{ILLMProvider-DNhyOYoK.d.ts → ILLMProvider-B8ROITYK.d.ts} +59 -8
  4. package/dist/handlers/index.d.mts +2 -2
  5. package/dist/handlers/index.d.ts +2 -2
  6. package/dist/handlers/index.js +2376 -489
  7. package/dist/handlers/index.mjs +2372 -489
  8. package/dist/{index-CjQdL0cX.d.ts → index-B8PbEFSY.d.mts} +17 -3
  9. package/dist/{index-CHL1jdYm.d.mts → index-BCPKUAVL.d.ts} +33 -41
  10. package/dist/{index-Hgbwl9X4.d.ts → index-CrxCy36A.d.mts} +33 -41
  11. package/dist/{index-C9v7-tWd.d.mts → index-DNvoi-sV.d.ts} +17 -3
  12. package/dist/index.css +695 -203
  13. package/dist/index.d.mts +37 -7
  14. package/dist/index.d.ts +37 -7
  15. package/dist/index.js +1197 -286
  16. package/dist/index.mjs +1221 -297
  17. package/dist/server.d.mts +62 -6
  18. package/dist/server.d.ts +62 -6
  19. package/dist/server.js +2526 -574
  20. package/dist/server.mjs +2517 -573
  21. package/package.json +12 -10
  22. package/src/app/constants.tsx +207 -212
  23. package/src/app/layout.tsx +4 -28
  24. package/src/app/types.ts +17 -17
  25. package/src/components/AmbientBackground.tsx +10 -10
  26. package/src/components/ArchitectureCard.tsx +43 -7
  27. package/src/components/ArchitectureCardsSection.tsx +37 -4
  28. package/src/components/ChatWidget.tsx +4 -1
  29. package/src/components/ChatWindow.tsx +9 -2
  30. package/src/components/CodeViewer.tsx +19 -14
  31. package/src/components/DocViewer.tsx +75 -15
  32. package/src/components/Documentation.tsx +111 -28
  33. package/src/components/Hero.tsx +103 -20
  34. package/src/components/Lifecycle.tsx +65 -25
  35. package/src/components/MarkdownComponents.tsx +44 -1
  36. package/src/components/MessageBubble.tsx +162 -50
  37. package/src/components/constants.tsx +279 -0
  38. package/src/config/RagConfig.ts +56 -10
  39. package/src/config/constants.ts +5 -0
  40. package/src/config/serverConfig.ts +15 -0
  41. package/src/core/ConfigResolver.ts +30 -25
  42. package/src/core/DatabaseStorage.ts +469 -0
  43. package/src/core/LicenseVerifier.ts +154 -0
  44. package/src/core/MultiAgentCoordinator.ts +239 -0
  45. package/src/core/Pipeline.ts +148 -16
  46. package/src/core/ProviderRegistry.ts +5 -5
  47. package/src/core/Retrivora.ts +52 -6
  48. package/src/core/VectorPlugin.ts +74 -11
  49. package/src/core/mcp.ts +261 -0
  50. package/src/exceptions/index.ts +52 -0
  51. package/src/handlers/index.ts +504 -47
  52. package/src/hooks/useRagChat.ts +100 -43
  53. package/src/hooks/useStoredMessages.ts +15 -4
  54. package/src/index.ts +7 -0
  55. package/src/llm/LLMFactory.ts +15 -6
  56. package/src/llm/providers/GroqProvider.ts +176 -0
  57. package/src/llm/providers/QwenProvider.ts +191 -0
  58. package/src/server.ts +23 -13
  59. package/src/types/chat.ts +16 -0
  60. package/src/types/props.ts +50 -1
  61. package/.env.example +0 -80
  62. package/LICENSE.txt +0 -21
@@ -63,6 +63,7 @@ export function useRagChat(
63
63
  ): UseRagChatReturn {
64
64
  const {
65
65
  apiUrl = '/api/chat',
66
+ retrivoraApiBase = '/api/retrivora',
66
67
  namespace,
67
68
  persist = true,
68
69
  onReply,
@@ -89,9 +90,13 @@ export function useRagChat(
89
90
 
90
91
  lastInputRef.current = trimmed;
91
92
 
93
+ const userMsgId = opts?.skipUserMessage
94
+ ? (messagesRef.current.filter((m) => m.role === 'user').pop()?.id || `user_${Date.now()}`)
95
+ : `user_${Date.now()}`;
96
+
92
97
  if (!opts?.skipUserMessage) {
93
98
  const userMessage: RagMessage = {
94
- id: `user_${Date.now()}`,
99
+ id: userMsgId,
95
100
  role: 'user',
96
101
  content: trimmed,
97
102
  createdAt: new Date().toISOString(),
@@ -112,15 +117,22 @@ export function useRagChat(
112
117
 
113
118
  try {
114
119
  const history = messagesRef.current.map(({ role, content }) => ({ role, content }));
120
+ const assistantMessageId = `assistant_${Date.now()}`;
115
121
 
116
122
  const response = await fetch(apiUrl, {
117
123
  method: 'POST',
118
- headers: { 'Content-Type': 'application/json' },
124
+ headers: {
125
+ 'Content-Type': 'application/json',
126
+ ...(options.headers || {})
127
+ },
119
128
  signal: controller.signal,
120
129
  body: JSON.stringify({
121
130
  message: trimmed,
122
131
  history,
123
132
  namespace: namespace ?? projectId,
133
+ sessionId: options.sessionId || 'default',
134
+ messageId: assistantMessageId,
135
+ userMessageId: userMsgId,
124
136
  }),
125
137
  });
126
138
 
@@ -177,7 +189,6 @@ export function useRagChat(
177
189
  // ─────────────────────────────────────────────────────────────────
178
190
 
179
191
  // Add a placeholder assistant message
180
- const assistantMessageId = `assistant_${Date.now()}`;
181
192
  setMessages((prev) => [
182
193
  ...prev,
183
194
  {
@@ -193,58 +204,34 @@ export function useRagChat(
193
204
  if (done) break;
194
205
 
195
206
  buffer += decoder.decode(value, { stream: true });
207
+ const frames = parseSseChunk(buffer);
208
+
209
+ // Clear processed frames from buffer (approximate by matching last found frame boundary)
210
+ const lastIndex = buffer.lastIndexOf('\n\n');
211
+ if (lastIndex !== -1) {
212
+ buffer = buffer.slice(lastIndex + 2);
213
+ }
196
214
 
197
- const lastBoundary = buffer.lastIndexOf('\n\n');
198
- if (lastBoundary === -1) continue;
199
-
200
- const complete = buffer.slice(0, lastBoundary + 2);
201
- buffer = buffer.slice(lastBoundary + 2);
202
-
203
- let hasNonTextFrame = false;
204
-
205
- for (const frame of parseSseChunk(complete)) {
206
- if (frame.type === 'text' && frame.text) {
215
+ for (const frame of frames) {
216
+ if (frame.type === 'text') {
207
217
  assistantContent += frame.text;
208
218
  pendingContentRef.current = assistantContent;
209
- // Schedule a batched RAF flush instead of immediate setMessages
210
219
  scheduleFlush(assistantMessageId);
211
- } else if (frame.type === 'thinking' && frame.text) {
220
+ } else if (frame.type === 'thinking') {
212
221
  thinkingContent += frame.text;
213
222
  pendingThinkingRef.current = thinkingContent;
214
223
  scheduleFlush(assistantMessageId);
215
224
  } else if (frame.type === 'metadata') {
216
225
  sources = frame.sources ?? [];
217
- thinkingMs = (frame as any).thinkingMs;
218
- hasNonTextFrame = true;
226
+ thinkingMs = frame.thinkingMs;
219
227
  } else if (frame.type === 'ui_transformation') {
220
228
  uiTransformation = frame.data;
221
- hasNonTextFrame = true;
222
229
  } else if (frame.type === 'observability') {
223
- trace = (frame as SseObservabilityFrame).data;
224
- hasNonTextFrame = true;
230
+ trace = frame.data;
225
231
  } else if (frame.type === 'error') {
226
- setError(frame.error || 'Stream error');
232
+ throw new Error(frame.error);
227
233
  }
228
234
  }
229
-
230
- // Flush non-text metadata immediately (sources, UI transform, trace)
231
- if (hasNonTextFrame) {
232
- pendingFlush = false; // cancel pending text flush — we'll do a full update
233
- setMessages((prev) =>
234
- prev.map((msg) =>
235
- msg.id === assistantMessageId
236
- ? {
237
- ...msg,
238
- content: assistantContent,
239
- thinking: thinkingContent || undefined,
240
- thinkingMs: thinkingMs,
241
- sources: sources.length > 0 ? sources : msg.sources,
242
- uiTransformation: uiTransformation || (msg as RagMessage).uiTransformation,
243
- }
244
- : msg
245
- )
246
- );
247
- }
248
235
  }
249
236
 
250
237
  // Process any remaining buffered data
@@ -254,9 +241,9 @@ export function useRagChat(
254
241
  else if (frame.type === 'thinking' && frame.text) thinkingContent += frame.text;
255
242
  else if (frame.type === 'metadata') {
256
243
  sources = frame.sources ?? [];
257
- thinkingMs = (frame as any).thinkingMs;
244
+ thinkingMs = frame.thinkingMs;
258
245
  }
259
- else if (frame.type === 'observability') trace = (frame as SseObservabilityFrame).data;
246
+ else if (frame.type === 'observability') trace = frame.data;
260
247
  }
261
248
  }
262
249
 
@@ -289,7 +276,7 @@ export function useRagChat(
289
276
  abortControllerRef.current = null;
290
277
  }
291
278
  },
292
- [apiUrl, isLoading, namespace, onError, onReply, projectId, setMessages]
279
+ [apiUrl, isLoading, namespace, onError, onReply, projectId, setMessages, options.headers, options.sessionId]
293
280
  );
294
281
 
295
282
  const clear = useCallback(() => {
@@ -315,6 +302,73 @@ export function useRagChat(
315
302
  setIsLoading(false);
316
303
  }, []);
317
304
 
305
+ const loadHistory = useCallback(async (sessionId: string) => {
306
+ setIsLoading(true);
307
+ setError(null);
308
+ try {
309
+ const res = await fetch(`${retrivoraApiBase}/history?sessionId=${encodeURIComponent(sessionId)}`);
310
+ if (!res.ok) throw new Error(`Failed to load history: ${res.statusText}`);
311
+ const data = await res.json();
312
+ const loadedMessages = (data.history || []).map((msg: any) => ({
313
+ id: msg.id,
314
+ role: msg.role,
315
+ content: msg.content,
316
+ sources: msg.sources,
317
+ uiTransformation: msg.uiTransformation,
318
+ trace: msg.trace,
319
+ createdAt: msg.createdAt,
320
+ }));
321
+ setMessages(loadedMessages);
322
+ } catch (err) {
323
+ const msg = err instanceof Error ? err.message : 'Failed to load history';
324
+ setError(msg);
325
+ onError?.(msg);
326
+ } finally {
327
+ setIsLoading(false);
328
+ }
329
+ }, [retrivoraApiBase, onError, setMessages]);
330
+
331
+ const clearHistory = useCallback(async (sessionId: string) => {
332
+ setIsLoading(true);
333
+ setError(null);
334
+ try {
335
+ const res = await fetch(`${retrivoraApiBase}/history/clear`, {
336
+ method: 'POST',
337
+ headers: { 'Content-Type': 'application/json' },
338
+ body: JSON.stringify({ sessionId }),
339
+ });
340
+ if (!res.ok) throw new Error(`Failed to clear history: ${res.statusText}`);
341
+ setMessages([]);
342
+ if (persist) localStorage.removeItem(storageKey);
343
+ } catch (err) {
344
+ const msg = err instanceof Error ? err.message : 'Failed to clear history';
345
+ setError(msg);
346
+ onError?.(msg);
347
+ } finally {
348
+ setIsLoading(false);
349
+ }
350
+ }, [retrivoraApiBase, onError, persist, setMessages, storageKey]);
351
+
352
+ const submitFeedback = useCallback(async (messageId: string, rating: 'thumbs_up' | 'thumbs_down', comment?: string) => {
353
+ try {
354
+ const res = await fetch(`${retrivoraApiBase}/feedback`, {
355
+ method: 'POST',
356
+ headers: { 'Content-Type': 'application/json' },
357
+ body: JSON.stringify({
358
+ messageId,
359
+ sessionId: options.sessionId || 'default',
360
+ rating,
361
+ comment,
362
+ }),
363
+ });
364
+ if (!res.ok) throw new Error(`Failed to submit feedback: ${res.statusText}`);
365
+ } catch (err) {
366
+ const msg = err instanceof Error ? err.message : 'Failed to submit feedback';
367
+ onError?.(msg);
368
+ throw err;
369
+ }
370
+ }, [retrivoraApiBase, onError, options.sessionId]);
371
+
318
372
  return {
319
373
  messages,
320
374
  isLoading,
@@ -324,5 +378,8 @@ export function useRagChat(
324
378
  retry,
325
379
  setMessages,
326
380
  stop,
381
+ loadHistory,
382
+ clearHistory,
383
+ submitFeedback,
327
384
  };
328
385
  }
@@ -32,12 +32,23 @@ export function useStoredMessages<T extends StoredMessage>(
32
32
  storageKey: string,
33
33
  persist: boolean
34
34
  ): [T[], React.Dispatch<React.SetStateAction<T[]>>] {
35
- const [messages, setMessages] = React.useState<T[]>(() => (
36
- persist ? (readStoredMessages(storageKey) as T[]) : []
37
- ));
35
+ const [messages, setMessages] = React.useState<T[]>([]);
36
+ const isLoaded = React.useRef(false);
38
37
 
38
+ // Load from localStorage only on client-side mount to prevent hydration mismatch
39
39
  React.useEffect(() => {
40
- if (!persist || typeof window === 'undefined') {
40
+ if (persist) {
41
+ const loaded = readStoredMessages(storageKey) as T[];
42
+ if (loaded.length > 0) {
43
+ setMessages(loaded);
44
+ }
45
+ }
46
+ isLoaded.current = true;
47
+ }, [storageKey, persist]);
48
+
49
+ // Save to localStorage only after initialization has completed to avoid overwriting with empty array
50
+ React.useEffect(() => {
51
+ if (!isLoaded.current || !persist || typeof window === 'undefined') {
41
52
  return;
42
53
  }
43
54
 
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  /**
2
4
  * Package entry point — public API exported to consumers of this npm package.
3
5
  * This entry point is CLIENT-SAFE and can be imported in React Client Components.
@@ -13,6 +15,11 @@ export { MessageBubble } from './components/MessageBubble';
13
15
  export { SourceCard } from './components/SourceCard';
14
16
  export { ObservabilityPanel } from './components/ObservabilityPanel';
15
17
  export { ConfigProvider, useConfig } from './components/ConfigProvider';
18
+ export { Hero } from './components/Hero';
19
+ export { Lifecycle } from './components/Lifecycle';
20
+ export { ArchitectureCardsSection } from './components/ArchitectureCardsSection';
21
+ export { Documentation } from './components/Documentation';
22
+ export { AmbientBackground } from './components/AmbientBackground';
16
23
 
17
24
  // ── Headless Hooks ────────────────────────────────────────────
18
25
  export { useRagChat } from './hooks/useRagChat';
@@ -4,6 +4,8 @@ import { OpenAIProvider } from './providers/OpenAIProvider';
4
4
  import { AnthropicProvider } from './providers/AnthropicProvider';
5
5
  import { OllamaProvider } from './providers/OllamaProvider';
6
6
  import { GeminiProvider } from './providers/GeminiProvider';
7
+ import { GroqProvider } from './providers/GroqProvider';
8
+ import { QwenProvider } from './providers/QwenProvider';
7
9
  import { UniversalLLMAdapter } from './providers/UniversalLLMAdapter';
8
10
  import { IProviderValidator, IProviderHealthChecker } from '../core/ProviderInterfaces';
9
11
  import { ProviderNotFoundException } from '../exceptions';
@@ -53,7 +55,7 @@ export class LLMFactory {
53
55
  */
54
56
  static listProviders(): string[] {
55
57
  return [
56
- 'openai', 'anthropic', 'ollama', 'gemini', 'rest', 'universal_rest', 'custom',
58
+ 'openai', 'anthropic', 'ollama', 'gemini', 'groq', 'qwen', 'rest', 'universal_rest', 'custom',
57
59
  ...Array.from(customProviders.keys()),
58
60
  ];
59
61
  }
@@ -67,6 +69,10 @@ export class LLMFactory {
67
69
  return new OllamaProvider(llmConfig, embeddingConfig);
68
70
  case 'gemini':
69
71
  return new GeminiProvider(llmConfig, embeddingConfig);
72
+ case 'groq':
73
+ return new GroqProvider(llmConfig, embeddingConfig);
74
+ case 'qwen':
75
+ return new QwenProvider(llmConfig, embeddingConfig);
70
76
  case 'rest':
71
77
  case 'universal_rest':
72
78
  case 'custom':
@@ -83,11 +89,12 @@ export class LLMFactory {
83
89
  return new UniversalLLMAdapter(llmConfig);
84
90
  }
85
91
  throw new ProviderNotFoundException(
86
- 'LLM',
87
- String(llmConfig.provider),
88
- `[LLMFactory] Unknown provider "${llmConfig.provider}". ` +
89
- `Built-in providers: ${LLMFactory.listProviders().join(', ')}. ` +
90
- `Register a custom provider with LLMFactory.register().`
92
+ 'llm',
93
+ llmConfig.provider ?? 'undefined',
94
+ {
95
+ message: `Unknown provider "${llmConfig.provider}". Register a custom provider with LLMFactory.register().`,
96
+ available: LLMFactory.listProviders()
97
+ }
91
98
  );
92
99
  }
93
100
  }
@@ -109,6 +116,8 @@ export class LLMFactory {
109
116
  case 'anthropic': return AnthropicProvider;
110
117
  case 'ollama': return OllamaProvider;
111
118
  case 'gemini': return GeminiProvider;
119
+ case 'groq': return GroqProvider;
120
+ case 'qwen': return QwenProvider;
112
121
  case 'rest':
113
122
  case 'universal_rest':
114
123
  case 'custom': return UniversalLLMAdapter as unknown as LLMProviderStatic;
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Groq LLM Provider
3
+ */
4
+
5
+ import OpenAI from 'openai';
6
+ import { ILLMProvider, EmbedOptions } from '../ILLMProvider';
7
+ import { ChatMessage, ChatOptions } from '../../types';
8
+ import { LLMConfig, EmbeddingConfig } from '../../config/RagConfig';
9
+ import { IProviderValidator, IProviderHealthChecker, HealthCheckResult } from '../../core/ProviderInterfaces';
10
+ import { ValidationError } from '../../core/ConfigValidator';
11
+ import { buildSystemContent } from '../utils';
12
+
13
+ export class GroqProvider implements ILLMProvider {
14
+ private readonly client: OpenAI;
15
+ private readonly llmConfig: LLMConfig;
16
+
17
+ constructor(llmConfig: LLMConfig, embeddingConfig?: EmbeddingConfig) {
18
+ void embeddingConfig;
19
+ const apiKey = llmConfig.apiKey || process.env.GROQ_API_KEY;
20
+ if (!apiKey) throw new Error('[GroqProvider] llmConfig.apiKey or GROQ_API_KEY environment variable is required');
21
+
22
+ this.client = new OpenAI({
23
+ apiKey,
24
+ baseURL: llmConfig.baseUrl || 'https://api.groq.com/openai/v1',
25
+ });
26
+ this.llmConfig = llmConfig;
27
+ }
28
+
29
+ static getValidator(): IProviderValidator {
30
+ return {
31
+ validate(config: Record<string, unknown>): ValidationError[] {
32
+ const errors: ValidationError[] = [];
33
+ if (!config.apiKey && !process.env.GROQ_API_KEY) {
34
+ errors.push({
35
+ field: 'llm.apiKey',
36
+ message: 'Groq API key is required',
37
+ suggestion: 'Set GROQ_API_KEY environment variable',
38
+ severity: 'error',
39
+ });
40
+ }
41
+ if (!config.model) {
42
+ errors.push({
43
+ field: 'llm.model',
44
+ message: 'Groq model name is required',
45
+ suggestion: 'e.g., "llama-3.3-70b-versatile"',
46
+ severity: 'error',
47
+ });
48
+ }
49
+ return errors;
50
+ }
51
+ };
52
+ }
53
+
54
+ static getHealthChecker(): IProviderHealthChecker {
55
+ return {
56
+ async check(config: Record<string, unknown>): Promise<HealthCheckResult> {
57
+ const timestamp = Date.now();
58
+ const apiKey = (config.apiKey as string) || process.env.GROQ_API_KEY || '';
59
+ const modelName = config.model as string;
60
+
61
+ try {
62
+ const OpenAI = await import('openai');
63
+ const client = new OpenAI.default({
64
+ apiKey,
65
+ baseURL: (config.baseUrl as string) || 'https://api.groq.com/openai/v1',
66
+ });
67
+ const models = await client.models.list();
68
+ const hasModel = models.data.some((m) => m.id === modelName);
69
+ return {
70
+ healthy: true,
71
+ provider: 'groq',
72
+ capabilities: { model: modelName, available: hasModel, totalModels: models.data.length },
73
+ timestamp,
74
+ };
75
+ } catch (error) {
76
+ return {
77
+ healthy: false,
78
+ provider: 'groq',
79
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
80
+ timestamp,
81
+ };
82
+ }
83
+ }
84
+ };
85
+ }
86
+
87
+ async chat(messages: ChatMessage[], context: string, options?: ChatOptions): Promise<string> {
88
+ const basePrompt =
89
+ options?.systemPrompt ??
90
+ this.llmConfig.systemPrompt ??
91
+ 'You are a helpful assistant. Answer questions based on the provided context.';
92
+
93
+ const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam = {
94
+ role: 'system',
95
+ content: buildSystemContent(basePrompt, context),
96
+ };
97
+
98
+ const formattedMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
99
+ systemMessage,
100
+ ...messages.map((m) => ({
101
+ role: m.role as 'user' | 'assistant',
102
+ content: m.content,
103
+ })),
104
+ ];
105
+
106
+ const completion = await this.client.chat.completions.create({
107
+ model: this.llmConfig.model,
108
+ messages: formattedMessages,
109
+ max_tokens: options?.maxTokens ?? this.llmConfig.maxTokens ?? 1024,
110
+ temperature: options?.temperature ?? this.llmConfig.temperature ?? 0.7,
111
+ stop: options?.stop,
112
+ });
113
+
114
+ return completion.choices[0]?.message?.content ?? '';
115
+ }
116
+
117
+ async *chatStream(messages: ChatMessage[], context: string, options?: ChatOptions): AsyncIterable<string> {
118
+ const basePrompt =
119
+ options?.systemPrompt ??
120
+ this.llmConfig.systemPrompt ??
121
+ 'You are a helpful assistant. Answer questions based on the provided context.';
122
+
123
+ const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam = {
124
+ role: 'system',
125
+ content: buildSystemContent(basePrompt, context),
126
+ };
127
+
128
+ const formattedMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
129
+ systemMessage,
130
+ ...messages.map((m) => ({
131
+ role: m.role as 'user' | 'assistant',
132
+ content: m.content,
133
+ })),
134
+ ];
135
+
136
+ const stream = await this.client.chat.completions.create({
137
+ model: this.llmConfig.model,
138
+ messages: formattedMessages,
139
+ max_tokens: options?.maxTokens ?? this.llmConfig.maxTokens ?? 1024,
140
+ temperature: options?.temperature ?? this.llmConfig.temperature ?? 0.7,
141
+ stop: options?.stop,
142
+ stream: true,
143
+ });
144
+
145
+ if (!stream) {
146
+ throw new Error('[GroqProvider] completions.create stream is undefined');
147
+ }
148
+
149
+ for await (const chunk of stream) {
150
+ const content = chunk.choices[0]?.delta?.content || '';
151
+ if (content) yield content;
152
+ }
153
+ }
154
+
155
+ async embed(text: string, options?: EmbedOptions): Promise<number[]> {
156
+ void text;
157
+ void options;
158
+ throw new Error('[GroqProvider] Groq does not provide a native embedding API. Please configure "openai" or "gemini" for embeddings in RagConfig.');
159
+ }
160
+
161
+ async batchEmbed(texts: string[], options?: EmbedOptions): Promise<number[][]> {
162
+ void texts;
163
+ void options;
164
+ throw new Error('[GroqProvider] Groq does not provide a native embedding API. Please configure "openai" or "gemini" for embeddings in RagConfig.');
165
+ }
166
+
167
+ async ping(): Promise<boolean> {
168
+ try {
169
+ await this.client.models.list();
170
+ return true;
171
+ } catch (err) {
172
+ console.error('[GroqProvider] Ping failed:', err);
173
+ return false;
174
+ }
175
+ }
176
+ }