@umituz/web-ai-groq-provider 1.0.1

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.
@@ -0,0 +1,278 @@
1
+ /**
2
+ * useGroq Hook
3
+ * @description Main React hook for Groq text generation
4
+ */
5
+
6
+ import { useState, useCallback, useMemo } from "react";
7
+ import type { GroqGenerationConfig } from "../../domain/interfaces";
8
+ import type { TextGenerationOptions, StreamingCallbacks } from "../../domain/interfaces";
9
+ import { textGenerationService } from "../../infrastructure/services/text-generation.service";
10
+ import { groqHttpClient } from "../../infrastructure/services/http-client.service";
11
+ import { GroqError } from "../../infrastructure/utils/groq-error.util";
12
+ import { getUserFriendlyError } from "../../infrastructure/utils/error.util";
13
+ import { DEFAULT_MODELS } from "../../infrastructure/constants/groq.constants";
14
+
15
+ const isDevelopment = typeof process !== "undefined" && process.env?.NODE_ENV === "development";
16
+
17
+ export interface UseGroqOptions {
18
+ /** Initial model to use */
19
+ readonly model?: string;
20
+ /** Default generation config */
21
+ readonly generationConfig?: GroqGenerationConfig;
22
+ /** Callback when generation starts */
23
+ readonly onStart?: () => void;
24
+ /** Callback when generation completes */
25
+ readonly onSuccess?: (result: string) => void;
26
+ /** Callback when generation fails */
27
+ readonly onError?: (error: string) => void;
28
+ }
29
+
30
+ export interface UseGroqReturn {
31
+ /** Loading state */
32
+ readonly isLoading: boolean;
33
+ /** Error message */
34
+ readonly error: string | null;
35
+ /** Generated result */
36
+ readonly result: string | null;
37
+ /** Generate text from a prompt */
38
+ generate: (prompt: string, options?: GroqGenerationConfig) => Promise<string>;
39
+ /** Generate structured JSON output */
40
+ generateJSON: <T = Record<string, unknown>>(
41
+ prompt: string,
42
+ options?: GroqGenerationConfig & { schema?: Record<string, unknown> }
43
+ ) => Promise<T>;
44
+ /** Stream text generation */
45
+ stream: (
46
+ prompt: string,
47
+ onChunk: (chunk: string) => void,
48
+ options?: GroqGenerationConfig
49
+ ) => Promise<void>;
50
+ /** Reset state */
51
+ reset: () => void;
52
+ /** Clear error */
53
+ clearError: () => void;
54
+ }
55
+
56
+ /**
57
+ * Hook for Groq text generation
58
+ */
59
+ export function useGroq(options: UseGroqOptions = {}): UseGroqReturn {
60
+ const [isLoading, setIsLoading] = useState(false);
61
+ const [error, setError] = useState<string | null>(null);
62
+ const [result, setResult] = useState<string | null>(null);
63
+
64
+ // Memoize options to prevent unnecessary callback recreations
65
+ const stableOptions = useMemo(
66
+ () => options,
67
+ [
68
+ options.model,
69
+ options.generationConfig?.temperature,
70
+ options.generationConfig?.maxTokens,
71
+ options.generationConfig?.topP,
72
+ options.onStart,
73
+ options.onSuccess,
74
+ options.onError,
75
+ ]
76
+ );
77
+
78
+ const generate = useCallback(
79
+ async (prompt: string, config?: GroqGenerationConfig): Promise<string> => {
80
+ setIsLoading(true);
81
+ setError(null);
82
+ setResult(null);
83
+
84
+ if (isDevelopment) {
85
+ console.log("[useGroq] generate called:", {
86
+ promptLength: prompt.length,
87
+ });
88
+ }
89
+
90
+ stableOptions.onStart?.();
91
+
92
+ try {
93
+ const response = await textGenerationService.generateCompletion(prompt, {
94
+ model: stableOptions.model,
95
+ generationConfig: {
96
+ ...stableOptions.generationConfig,
97
+ ...config,
98
+ },
99
+ });
100
+
101
+ setResult(response);
102
+ stableOptions.onSuccess?.(response);
103
+
104
+ if (isDevelopment) {
105
+ console.log("[useGroq] generate success:", {
106
+ responseLength: response.length,
107
+ });
108
+ }
109
+
110
+ return response;
111
+ } catch (err) {
112
+ const errorMessage = getUserFriendlyError(
113
+ err instanceof Error ? err : new Error("Unknown error")
114
+ );
115
+ setError(errorMessage);
116
+ stableOptions.onError?.(errorMessage);
117
+
118
+ if (isDevelopment) {
119
+ console.error("[useGroq] generate error:", { error: err });
120
+ }
121
+
122
+ throw err;
123
+ } finally {
124
+ setIsLoading(false);
125
+ }
126
+ },
127
+ [stableOptions]
128
+ );
129
+
130
+ const generateJSON = useCallback(
131
+ async <T = Record<string, unknown>,>(
132
+ prompt: string,
133
+ config?: GroqGenerationConfig & { schema?: Record<string, unknown> }
134
+ ): Promise<T> => {
135
+ setIsLoading(true);
136
+ setError(null);
137
+ setResult(null);
138
+
139
+ if (isDevelopment) {
140
+ console.log("[useGroq] generateJSON called:", {
141
+ promptLength: prompt.length,
142
+ });
143
+ }
144
+
145
+ stableOptions.onStart?.();
146
+
147
+ try {
148
+ const response = await textGenerationService.generateStructured<T>(prompt, {
149
+ model: stableOptions.model,
150
+ generationConfig: {
151
+ ...stableOptions.generationConfig,
152
+ ...config,
153
+ },
154
+ schema: config?.schema,
155
+ });
156
+
157
+ const jsonString = JSON.stringify(response, null, 2);
158
+ setResult(jsonString);
159
+ stableOptions.onSuccess?.(jsonString);
160
+
161
+ if (isDevelopment) {
162
+ console.log("[useGroq] generateJSON success:", {
163
+ hasResponse: !!response,
164
+ });
165
+ }
166
+
167
+ return response;
168
+ } catch (err) {
169
+ const errorMessage = getUserFriendlyError(
170
+ err instanceof Error ? err : new Error("Unknown error")
171
+ );
172
+ setError(errorMessage);
173
+ stableOptions.onError?.(errorMessage);
174
+
175
+ if (isDevelopment) {
176
+ console.error("[useGroq] generateJSON error:", { error: err });
177
+ }
178
+
179
+ throw err;
180
+ } finally {
181
+ setIsLoading(false);
182
+ }
183
+ },
184
+ [stableOptions]
185
+ );
186
+
187
+ const stream = useCallback(
188
+ async (
189
+ prompt: string,
190
+ onChunk: (chunk: string) => void,
191
+ config?: GroqGenerationConfig
192
+ ): Promise<void> => {
193
+ setIsLoading(true);
194
+ setError(null);
195
+ setResult(null);
196
+
197
+ let fullContent = "";
198
+
199
+ if (isDevelopment) {
200
+ console.log("[useGroq] stream called:", {
201
+ promptLength: prompt.length,
202
+ });
203
+ }
204
+
205
+ stableOptions.onStart?.();
206
+
207
+ try {
208
+ const callbacks: StreamingCallbacks = {
209
+ onChunk: (c) => {
210
+ fullContent += c;
211
+ onChunk(c);
212
+ },
213
+ onComplete: (text) => {
214
+ setResult(text);
215
+ stableOptions.onSuccess?.(text);
216
+ },
217
+ };
218
+
219
+ for await (const _ of textGenerationService.streamCompletion(
220
+ prompt,
221
+ callbacks,
222
+ {
223
+ model: stableOptions.model,
224
+ generationConfig: {
225
+ ...stableOptions.generationConfig,
226
+ ...config,
227
+ },
228
+ }
229
+ )) {
230
+ // Consume the async generator
231
+ void _;
232
+ }
233
+
234
+ if (isDevelopment) {
235
+ console.log("[useGroq] stream success:", {
236
+ contentLength: fullContent.length,
237
+ });
238
+ }
239
+ } catch (err) {
240
+ const errorMessage = getUserFriendlyError(
241
+ err instanceof Error ? err : new Error("Unknown error")
242
+ );
243
+ setError(errorMessage);
244
+ stableOptions.onError?.(errorMessage);
245
+
246
+ if (isDevelopment) {
247
+ console.error("[useGroq] stream error:", { error: err });
248
+ }
249
+
250
+ throw err;
251
+ } finally {
252
+ setIsLoading(false);
253
+ }
254
+ },
255
+ [stableOptions]
256
+ );
257
+
258
+ const reset = useCallback(() => {
259
+ setIsLoading(false);
260
+ setError(null);
261
+ setResult(null);
262
+ }, []);
263
+
264
+ const clearError = useCallback(() => {
265
+ setError(null);
266
+ }, []);
267
+
268
+ return {
269
+ isLoading,
270
+ error,
271
+ result,
272
+ generate,
273
+ generateJSON,
274
+ stream,
275
+ reset,
276
+ clearError,
277
+ };
278
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Streaming Text Generation Service
3
+ * Handles streaming text generation using Groq API
4
+ */
5
+
6
+ import type {
7
+ GroqChatRequest,
8
+ GroqMessage,
9
+ GroqGenerationConfig,
10
+ GroqChatChunk,
11
+ } from "./types";
12
+ import { groqHttpClient } from "./client";
13
+ import { DEFAULT_MODELS } from "./types";
14
+ import { GroqError, GroqErrorType } from "./types";
15
+
16
+ const isDevelopment = process.env.NODE_ENV === "development";
17
+
18
+ export interface StreamingOptions {
19
+ model?: string;
20
+ generationConfig?: GroqGenerationConfig;
21
+ callbacks?: StreamingCallbacks;
22
+ }
23
+
24
+ export interface StreamingCallbacks {
25
+ onChunk?: (chunk: string) => void;
26
+ onComplete?: (fullText: string) => void;
27
+ onError?: (error: Error) => void;
28
+ }
29
+
30
+ /**
31
+ * Stream text generation from a prompt
32
+ * Returns an async generator of text chunks
33
+ */
34
+ export async function* streaming(
35
+ prompt: string,
36
+ options: StreamingOptions = {}
37
+ ): AsyncGenerator<string> {
38
+ const startTime = Date.now();
39
+ const model = options.model || DEFAULT_MODELS.TEXT;
40
+
41
+ if (isDevelopment) {
42
+ console.log("[Groq] streaming called:", {
43
+ model,
44
+ promptLength: prompt.length,
45
+ promptPreview: prompt.substring(0, 100) + "...",
46
+ });
47
+ }
48
+
49
+ const messages: GroqMessage[] = [
50
+ {
51
+ role: "user",
52
+ content: prompt,
53
+ },
54
+ ];
55
+
56
+ const request: GroqChatRequest = {
57
+ model,
58
+ messages,
59
+ temperature: options.generationConfig?.temperature || 0.7,
60
+ max_tokens: options.generationConfig?.maxTokens || 1024,
61
+ top_p: options.generationConfig?.topP,
62
+ n: options.generationConfig?.n,
63
+ stop: options.generationConfig?.stop,
64
+ frequency_penalty: options.generationConfig?.frequencyPenalty,
65
+ presence_penalty: options.generationConfig?.presencePenalty,
66
+ };
67
+
68
+ if (isDevelopment) {
69
+ console.log("[Groq] Starting streaming request");
70
+ }
71
+
72
+ try {
73
+ let fullText = "";
74
+
75
+ for await (const chunk of groqHttpClient.chatCompletionStream(request)) {
76
+ const content = chunk.choices[0]?.delta?.content;
77
+
78
+ if (content) {
79
+ fullText += content;
80
+ options.callbacks?.onChunk?.(content);
81
+ yield content;
82
+ }
83
+
84
+ // Check if generation is complete
85
+ if (chunk.choices[0]?.finish_reason) {
86
+ if (isDevelopment) {
87
+ console.log("[Groq] Streaming complete:", {
88
+ finishReason: chunk.choices[0].finish_reason,
89
+ totalLength: fullText.length,
90
+ });
91
+ }
92
+ options.callbacks?.onComplete?.(fullText);
93
+ break;
94
+ }
95
+ }
96
+
97
+ const totalDuration = Date.now() - startTime;
98
+ if (isDevelopment) {
99
+ console.log("[Groq] streaming complete:", {
100
+ totalDuration: `${totalDuration}ms`,
101
+ totalLength: fullText.length,
102
+ });
103
+ }
104
+ } catch (error) {
105
+ if (isDevelopment) {
106
+ console.error("[Groq] streaming error:", {
107
+ error,
108
+ errorMessage: error instanceof Error ? error.message : String(error),
109
+ });
110
+ }
111
+
112
+ options.callbacks?.onError?.(error as Error);
113
+ throw error;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Stream chat generation from messages
119
+ * Returns an async generator of text chunks
120
+ */
121
+ export async function* streamingChat(
122
+ messages: GroqMessage[],
123
+ options: StreamingOptions = {}
124
+ ): AsyncGenerator<string> {
125
+ const startTime = Date.now();
126
+ const model = options.model || DEFAULT_MODELS.TEXT;
127
+
128
+ if (isDevelopment) {
129
+ console.log("[Groq] streamingChat called:", {
130
+ model,
131
+ messageCount: messages.length,
132
+ });
133
+ }
134
+
135
+ const request: GroqChatRequest = {
136
+ model,
137
+ messages,
138
+ temperature: options.generationConfig?.temperature || 0.7,
139
+ max_tokens: options.generationConfig?.maxTokens || 1024,
140
+ top_p: options.generationConfig?.topP,
141
+ n: options.generationConfig?.n,
142
+ stop: options.generationConfig?.stop,
143
+ frequency_penalty: options.generationConfig?.frequencyPenalty,
144
+ presence_penalty: options.generationConfig?.presencePenalty,
145
+ };
146
+
147
+ if (isDevelopment) {
148
+ console.log("[Groq] Starting streaming chat request");
149
+ }
150
+
151
+ try {
152
+ let fullText = "";
153
+
154
+ for await (const chunk of groqHttpClient.chatCompletionStream(request)) {
155
+ const content = chunk.choices[0]?.delta?.content;
156
+
157
+ if (content) {
158
+ fullText += content;
159
+ options.callbacks?.onChunk?.(content);
160
+ yield content;
161
+ }
162
+
163
+ // Check if generation is complete
164
+ if (chunk.choices[0]?.finish_reason) {
165
+ if (isDevelopment) {
166
+ console.log("[Groq] Streaming chat complete:", {
167
+ finishReason: chunk.choices[0].finish_reason,
168
+ totalLength: fullText.length,
169
+ });
170
+ }
171
+ options.callbacks?.onComplete?.(fullText);
172
+ break;
173
+ }
174
+ }
175
+
176
+ const totalDuration = Date.now() - startTime;
177
+ if (isDevelopment) {
178
+ console.log("[Groq] streamingChat complete:", {
179
+ totalDuration: `${totalDuration}ms`,
180
+ totalLength: fullText.length,
181
+ });
182
+ }
183
+ } catch (error) {
184
+ if (isDevelopment) {
185
+ console.error("[Groq] streamingChat error:", {
186
+ error,
187
+ errorMessage: error instanceof Error ? error.message : String(error),
188
+ });
189
+ }
190
+
191
+ options.callbacks?.onError?.(error as Error);
192
+ throw error;
193
+ }
194
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Structured Text Generation Service
3
+ * Handles JSON/structured output generation using Groq API
4
+ */
5
+
6
+ import type {
7
+ GroqMessage,
8
+ GroqGenerationConfig,
9
+ } from "./types";
10
+ import { textGeneration } from "./text-generation";
11
+ import { GroqError, GroqErrorType } from "./types";
12
+
13
+ const isDevelopment = process.env.NODE_ENV === "development";
14
+
15
+ export interface StructuredTextOptions<T = unknown> {
16
+ model?: string;
17
+ generationConfig?: GroqGenerationConfig;
18
+ schema?: Record<string, unknown>;
19
+ }
20
+
21
+ /**
22
+ * Generate structured JSON output from a prompt
23
+ */
24
+ export async function structuredText<T = Record<string, unknown>>(
25
+ prompt: string,
26
+ options: StructuredTextOptions<T> = {}
27
+ ): Promise<T> {
28
+ const startTime = Date.now();
29
+
30
+ if (isDevelopment) {
31
+ console.log("[Groq] structuredText called:", {
32
+ model: options.model,
33
+ promptLength: prompt.length,
34
+ hasSchema: !!options.schema,
35
+ });
36
+ }
37
+
38
+ // Build a prompt that encourages JSON output
39
+ let enhancedPrompt = prompt;
40
+
41
+ if (options.schema) {
42
+ enhancedPrompt = `
43
+ ${prompt}
44
+
45
+ Please respond with a JSON object that follows this schema:
46
+ ${JSON.stringify(options.schema, null, 2)}
47
+
48
+ Your response must be valid JSON only, with no additional text or formatting.
49
+ `;
50
+ } else {
51
+ enhancedPrompt = `
52
+ ${prompt}
53
+
54
+ Please respond with a valid JSON object. Your response must be valid JSON only, with no additional text or formatting.
55
+ `;
56
+ }
57
+
58
+ try {
59
+ // Generate text with lower temperature for more consistent structured output
60
+ const response = await textGeneration(enhancedPrompt, {
61
+ model: options.model,
62
+ generationConfig: {
63
+ temperature: 0.3, // Lower temperature for more deterministic output
64
+ maxTokens: 2048,
65
+ ...options.generationConfig,
66
+ },
67
+ });
68
+
69
+ if (isDevelopment) {
70
+ console.log("[Groq] structuredText response received:", {
71
+ responseLength: response.length,
72
+ responsePreview: response.substring(0, 200) + "...",
73
+ });
74
+ }
75
+
76
+ // Extract JSON from response (handle markdown code blocks)
77
+ let jsonStr = response.trim();
78
+
79
+ // Remove markdown code blocks if present
80
+ if (jsonStr.startsWith("```json")) {
81
+ jsonStr = jsonStr.slice(7);
82
+ } else if (jsonStr.startsWith("```")) {
83
+ jsonStr = jsonStr.slice(3);
84
+ }
85
+
86
+ if (jsonStr.endsWith("```")) {
87
+ jsonStr = jsonStr.slice(0, -3);
88
+ }
89
+
90
+ jsonStr = jsonStr.trim();
91
+
92
+ // Parse JSON
93
+ const parsed = JSON.parse(jsonStr) as T;
94
+
95
+ const totalDuration = Date.now() - startTime;
96
+ if (isDevelopment) {
97
+ console.log("[Groq] structuredText complete:", {
98
+ totalDuration: `${totalDuration}ms`,
99
+ parsed: !!parsed,
100
+ });
101
+ }
102
+
103
+ return parsed;
104
+ } catch (error) {
105
+ if (isDevelopment) {
106
+ console.error("[Groq] structuredText error:", {
107
+ error,
108
+ errorMessage: error instanceof Error ? error.message : String(error),
109
+ });
110
+ }
111
+
112
+ throw new GroqError(
113
+ GroqErrorType.UNKNOWN_ERROR,
114
+ `Failed to generate structured output: ${
115
+ error instanceof Error ? error.message : "Unknown error"
116
+ }`,
117
+ error as Error
118
+ );
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Generate structured JSON output from chat messages
124
+ */
125
+ export async function structuredChat<T = Record<string, unknown>>(
126
+ messages: GroqMessage[],
127
+ options: StructuredTextOptions<T> = {}
128
+ ): Promise<T> {
129
+ const startTime = Date.now();
130
+
131
+ if (isDevelopment) {
132
+ console.log("[Groq] structuredChat called:", {
133
+ model: options.model,
134
+ messageCount: messages.length,
135
+ hasSchema: !!options.schema,
136
+ });
137
+ }
138
+
139
+ // Add a system message requesting JSON output
140
+ const systemMessage: GroqMessage = {
141
+ role: "system",
142
+ content: options.schema
143
+ ? `You must respond with valid JSON that follows this schema:\n${JSON.stringify(options.schema, null, 2)}\n\nYour response must be valid JSON only, with no additional text or formatting.`
144
+ : "You must respond with valid JSON only. Your response must be valid JSON only, with no additional text or formatting.",
145
+ };
146
+
147
+ const enhancedMessages = [systemMessage, ...messages];
148
+
149
+ try {
150
+ const { chatGeneration } = await import("./text-generation");
151
+
152
+ // Generate with lower temperature for more deterministic output
153
+ const response = await chatGeneration(enhancedMessages, {
154
+ model: options.model,
155
+ generationConfig: {
156
+ temperature: 0.3,
157
+ maxTokens: 2048,
158
+ ...options.generationConfig,
159
+ },
160
+ });
161
+
162
+ if (isDevelopment) {
163
+ console.log("[Groq] structuredChat response received:", {
164
+ responseLength: response.length,
165
+ });
166
+ }
167
+
168
+ // Extract JSON from response
169
+ let jsonStr = response.trim();
170
+
171
+ // Remove markdown code blocks if present
172
+ if (jsonStr.startsWith("```json")) {
173
+ jsonStr = jsonStr.slice(7);
174
+ } else if (jsonStr.startsWith("```")) {
175
+ jsonStr = jsonStr.slice(3);
176
+ }
177
+
178
+ if (jsonStr.endsWith("```")) {
179
+ jsonStr = jsonStr.slice(0, -3);
180
+ }
181
+
182
+ jsonStr = jsonStr.trim();
183
+
184
+ // Parse JSON
185
+ const parsed = JSON.parse(jsonStr) as T;
186
+
187
+ const totalDuration = Date.now() - startTime;
188
+ if (isDevelopment) {
189
+ console.log("[Groq] structuredChat complete:", {
190
+ totalDuration: `${totalDuration}ms`,
191
+ });
192
+ }
193
+
194
+ return parsed;
195
+ } catch (error) {
196
+ if (isDevelopment) {
197
+ console.error("[Groq] structuredChat error:", {
198
+ error,
199
+ errorMessage: error instanceof Error ? error.message : String(error),
200
+ });
201
+ }
202
+
203
+ throw new GroqError(
204
+ GroqErrorType.UNKNOWN_ERROR,
205
+ `Failed to generate structured output: ${
206
+ error instanceof Error ? error.message : "Unknown error"
207
+ }`,
208
+ error as Error
209
+ );
210
+ }
211
+ }