@umituz/react-native-ai-groq-provider 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/LICENSE +21 -0
- package/README.md +266 -0
- package/package.json +96 -0
- package/src/domain/entities/error.types.ts +54 -0
- package/src/domain/entities/groq.types.ts +235 -0
- package/src/domain/entities/index.ts +7 -0
- package/src/domain/entities/models.ts +212 -0
- package/src/index.ts +113 -0
- package/src/infrastructure/services/ChatSession.ts +265 -0
- package/src/infrastructure/services/GroqClient.ts +280 -0
- package/src/infrastructure/services/Streaming.ts +114 -0
- package/src/infrastructure/services/StructuredText.ts +171 -0
- package/src/infrastructure/services/TextGeneration.ts +94 -0
- package/src/infrastructure/services/index.ts +19 -0
- package/src/infrastructure/telemetry/TelemetryHooks.ts +79 -0
- package/src/infrastructure/telemetry/index.ts +5 -0
- package/src/infrastructure/utils/async/execute-state.util.ts +76 -0
- package/src/infrastructure/utils/async/index.ts +6 -0
- package/src/infrastructure/utils/content-mapper.util.ts +86 -0
- package/src/infrastructure/utils/error-mapper.util.ts +77 -0
- package/src/infrastructure/utils/index.ts +7 -0
- package/src/presentation/hooks/useGroq.ts +224 -0
- package/src/presentation/hooks/useOperationManager.ts +110 -0
- package/src/providers/ConfigBuilder.ts +159 -0
- package/src/providers/ProviderFactory.ts +98 -0
- package/src/providers/index.ts +16 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Groq HTTP Client
|
|
3
|
+
* Handles all HTTP communication with Groq API
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
GroqConfig,
|
|
8
|
+
GroqChatRequest,
|
|
9
|
+
GroqChatResponse,
|
|
10
|
+
GroqChatChunk,
|
|
11
|
+
} from "../../domain/entities";
|
|
12
|
+
import { GroqError, GroqErrorType, mapHttpStatusToErrorType } from "../../domain/entities/error.types";
|
|
13
|
+
|
|
14
|
+
const DEFAULT_BASE_URL = "https://api.groq.com/openai/v1";
|
|
15
|
+
const DEFAULT_TIMEOUT = 60000; // 60 seconds
|
|
16
|
+
const CHAT_COMPLETIONS_ENDPOINT = "/chat/completions";
|
|
17
|
+
|
|
18
|
+
class GroqHttpClient {
|
|
19
|
+
private config: GroqConfig | null = null;
|
|
20
|
+
private initialized = false;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Initialize the client with configuration
|
|
24
|
+
*/
|
|
25
|
+
initialize(config: GroqConfig): void {
|
|
26
|
+
const apiKey = config.apiKey?.trim();
|
|
27
|
+
|
|
28
|
+
if (!apiKey || apiKey.length < 10) {
|
|
29
|
+
throw new GroqError(
|
|
30
|
+
GroqErrorType.INVALID_API_KEY,
|
|
31
|
+
"API key is required and must be at least 10 characters"
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.config = {
|
|
36
|
+
apiKey,
|
|
37
|
+
baseUrl: config.baseUrl || DEFAULT_BASE_URL,
|
|
38
|
+
timeoutMs: config.timeoutMs || DEFAULT_TIMEOUT,
|
|
39
|
+
textModel: config.textModel,
|
|
40
|
+
};
|
|
41
|
+
this.initialized = true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if client is initialized
|
|
46
|
+
*/
|
|
47
|
+
isInitialized(): boolean {
|
|
48
|
+
return this.initialized && this.config !== null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get current configuration
|
|
53
|
+
*/
|
|
54
|
+
getConfig(): GroqConfig {
|
|
55
|
+
if (!this.config || !this.initialized) {
|
|
56
|
+
throw new GroqError(
|
|
57
|
+
GroqErrorType.MISSING_CONFIG,
|
|
58
|
+
"Client not initialized. Call initialize() first."
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return this.config;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Make an HTTP request to Groq API
|
|
66
|
+
*/
|
|
67
|
+
private async request<T>(
|
|
68
|
+
endpoint: string,
|
|
69
|
+
body: unknown,
|
|
70
|
+
signal?: AbortSignal
|
|
71
|
+
): Promise<T> {
|
|
72
|
+
if (!this.config || !this.initialized) {
|
|
73
|
+
throw new GroqError(
|
|
74
|
+
GroqErrorType.MISSING_CONFIG,
|
|
75
|
+
"Client not initialized. Call initialize() first."
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const url = `${this.config.baseUrl}${endpoint}`;
|
|
80
|
+
const timeout = this.config.timeoutMs || DEFAULT_TIMEOUT;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
// Create AbortController for timeout
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
86
|
+
|
|
87
|
+
// Use provided signal if available
|
|
88
|
+
if (signal) {
|
|
89
|
+
signal.addEventListener("abort", () => controller.abort());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const response = await fetch(url, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: {
|
|
95
|
+
"Content-Type": "application/json",
|
|
96
|
+
"Authorization": `Bearer ${this.config.apiKey}`,
|
|
97
|
+
},
|
|
98
|
+
body: JSON.stringify(body),
|
|
99
|
+
signal: controller.signal,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
clearTimeout(timeoutId);
|
|
103
|
+
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
await this.handleErrorResponse(response);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return (await response.json()) as T;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw this.handleRequestError(error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Handle HTTP error responses
|
|
116
|
+
*/
|
|
117
|
+
private async handleErrorResponse(response: Response): Promise<never> {
|
|
118
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
119
|
+
let errorType = mapHttpStatusToErrorType(response.status);
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const errorData = (await response.json()) as { error?: { message?: string } };
|
|
123
|
+
if (errorData.error?.message) {
|
|
124
|
+
errorMessage = errorData.error.message;
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
// If parsing fails, use default message
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
throw new GroqError(errorType, errorMessage, {
|
|
131
|
+
status: response.status,
|
|
132
|
+
url: response.url,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Handle request errors (network, timeout, abort)
|
|
138
|
+
*/
|
|
139
|
+
private handleRequestError(error: unknown): GroqError {
|
|
140
|
+
if (error instanceof GroqError) {
|
|
141
|
+
return error;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (error instanceof Error) {
|
|
145
|
+
if (error.name === "AbortError") {
|
|
146
|
+
return new GroqError(
|
|
147
|
+
GroqErrorType.ABORT_ERROR,
|
|
148
|
+
"Request was aborted by the client",
|
|
149
|
+
error
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (error.name === "TypeError" && error.message.includes("network")) {
|
|
154
|
+
return new GroqError(
|
|
155
|
+
GroqErrorType.NETWORK_ERROR,
|
|
156
|
+
"Network error: Unable to connect to Groq API",
|
|
157
|
+
error
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return new GroqError(
|
|
163
|
+
GroqErrorType.UNKNOWN_ERROR,
|
|
164
|
+
error instanceof Error ? error.message : "Unknown error occurred",
|
|
165
|
+
error
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Send chat completion request (non-streaming)
|
|
171
|
+
*/
|
|
172
|
+
async chatCompletion(
|
|
173
|
+
request: GroqChatRequest,
|
|
174
|
+
signal?: AbortSignal
|
|
175
|
+
): Promise<GroqChatResponse> {
|
|
176
|
+
return this.request<GroqChatResponse>(
|
|
177
|
+
CHAT_COMPLETIONS_ENDPOINT,
|
|
178
|
+
{ ...request, stream: false },
|
|
179
|
+
signal
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Send chat completion request (streaming)
|
|
185
|
+
* Returns an async generator of chunks
|
|
186
|
+
*/
|
|
187
|
+
async *chatCompletionStream(
|
|
188
|
+
request: GroqChatRequest,
|
|
189
|
+
signal?: AbortSignal
|
|
190
|
+
): AsyncGenerator<GroqChatChunk> {
|
|
191
|
+
if (!this.config || !this.initialized) {
|
|
192
|
+
throw new GroqError(
|
|
193
|
+
GroqErrorType.MISSING_CONFIG,
|
|
194
|
+
"Client not initialized. Call initialize() first."
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const url = `${this.config.baseUrl}${CHAT_COMPLETIONS_ENDPOINT}`;
|
|
199
|
+
const timeout = this.config.timeoutMs || DEFAULT_TIMEOUT;
|
|
200
|
+
|
|
201
|
+
// Create AbortController for timeout
|
|
202
|
+
const controller = new AbortController();
|
|
203
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
204
|
+
|
|
205
|
+
// Use provided signal if available
|
|
206
|
+
if (signal) {
|
|
207
|
+
signal.addEventListener("abort", () => controller.abort());
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const response = await fetch(url, {
|
|
212
|
+
method: "POST",
|
|
213
|
+
headers: {
|
|
214
|
+
"Content-Type": "application/json",
|
|
215
|
+
"Authorization": `Bearer ${this.config.apiKey}`,
|
|
216
|
+
},
|
|
217
|
+
body: JSON.stringify({ ...request, stream: true }),
|
|
218
|
+
signal: controller.signal,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
clearTimeout(timeoutId);
|
|
222
|
+
|
|
223
|
+
if (!response.ok) {
|
|
224
|
+
await this.handleErrorResponse(response);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!response.body) {
|
|
228
|
+
throw new GroqError(
|
|
229
|
+
GroqErrorType.NETWORK_ERROR,
|
|
230
|
+
"Response body is null"
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Read and parse Server-Sent Events
|
|
235
|
+
const reader = response.body.getReader();
|
|
236
|
+
const decoder = new TextDecoder();
|
|
237
|
+
let buffer = "";
|
|
238
|
+
|
|
239
|
+
while (true) {
|
|
240
|
+
const { done, value } = await reader.read();
|
|
241
|
+
|
|
242
|
+
if (done) break;
|
|
243
|
+
|
|
244
|
+
buffer += decoder.decode(value, { stream: true });
|
|
245
|
+
const lines = buffer.split("\n");
|
|
246
|
+
buffer = lines.pop() || "";
|
|
247
|
+
|
|
248
|
+
for (const line of lines) {
|
|
249
|
+
const trimmed = line.trim();
|
|
250
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
251
|
+
|
|
252
|
+
if (trimmed.startsWith("data: ")) {
|
|
253
|
+
const jsonStr = trimmed.slice(6);
|
|
254
|
+
try {
|
|
255
|
+
const chunk = JSON.parse(jsonStr) as GroqChatChunk;
|
|
256
|
+
yield chunk;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.error("Failed to parse SSE chunk:", error);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
} catch (error) {
|
|
264
|
+
throw this.handleRequestError(error);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Reset the client
|
|
270
|
+
*/
|
|
271
|
+
reset(): void {
|
|
272
|
+
this.config = null;
|
|
273
|
+
this.initialized = false;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Singleton instance
|
|
279
|
+
*/
|
|
280
|
+
export const groqHttpClient = new GroqHttpClient();
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming Service
|
|
3
|
+
* Handles streaming responses from Groq API
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
GroqChatRequest,
|
|
8
|
+
GroqMessage,
|
|
9
|
+
GroqGenerationConfig,
|
|
10
|
+
} from "../../domain/entities";
|
|
11
|
+
import { groqHttpClient } from "./GroqClient";
|
|
12
|
+
import { DEFAULT_MODELS } from "../../domain/entities";
|
|
13
|
+
|
|
14
|
+
export interface StreamingCallbacks {
|
|
15
|
+
/** Called when new content chunk is received */
|
|
16
|
+
onChunk?: (chunk: string) => void;
|
|
17
|
+
/** Called when streaming completes */
|
|
18
|
+
onComplete?: (fullContent: string) => void;
|
|
19
|
+
/** Called when an error occurs */
|
|
20
|
+
onError?: (error: Error) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface StreamingOptions {
|
|
24
|
+
model?: string;
|
|
25
|
+
generationConfig?: GroqGenerationConfig;
|
|
26
|
+
callbacks?: StreamingCallbacks;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Stream text generation from a prompt
|
|
31
|
+
*/
|
|
32
|
+
export async function* streaming(
|
|
33
|
+
prompt: string,
|
|
34
|
+
options: StreamingOptions = {}
|
|
35
|
+
): AsyncGenerator<string> {
|
|
36
|
+
const model = options.model || DEFAULT_MODELS.TEXT;
|
|
37
|
+
|
|
38
|
+
const messages: GroqMessage[] = [
|
|
39
|
+
{
|
|
40
|
+
role: "user",
|
|
41
|
+
content: prompt,
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const request: GroqChatRequest = {
|
|
46
|
+
model,
|
|
47
|
+
messages,
|
|
48
|
+
temperature: options.generationConfig?.temperature || 0.7,
|
|
49
|
+
max_tokens: options.generationConfig?.maxTokens || 1024,
|
|
50
|
+
top_p: options.generationConfig?.topP,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
for await (const chunk of groqHttpClient.chatCompletionStream(request)) {
|
|
55
|
+
const content = chunk.choices[0]?.delta?.content;
|
|
56
|
+
if (content) {
|
|
57
|
+
options.callbacks?.onChunk?.(content);
|
|
58
|
+
yield content;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
options.callbacks?.onComplete?.(await collectStreamContent(request));
|
|
62
|
+
} catch (error) {
|
|
63
|
+
options.callbacks?.onError?.(error as Error);
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Stream chat generation from messages
|
|
70
|
+
*/
|
|
71
|
+
export async function* streamingChat(
|
|
72
|
+
messages: GroqMessage[],
|
|
73
|
+
options: StreamingOptions = {}
|
|
74
|
+
): AsyncGenerator<string> {
|
|
75
|
+
const model = options.model || DEFAULT_MODELS.TEXT;
|
|
76
|
+
|
|
77
|
+
const request: GroqChatRequest = {
|
|
78
|
+
model,
|
|
79
|
+
messages,
|
|
80
|
+
temperature: options.generationConfig?.temperature || 0.7,
|
|
81
|
+
max_tokens: options.generationConfig?.maxTokens || 1024,
|
|
82
|
+
top_p: options.generationConfig?.topP,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
for await (const chunk of groqHttpClient.chatCompletionStream(request)) {
|
|
87
|
+
const content = chunk.choices[0]?.delta?.content;
|
|
88
|
+
if (content) {
|
|
89
|
+
options.callbacks?.onChunk?.(content);
|
|
90
|
+
yield content;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
options.callbacks?.onComplete?.(await collectStreamContent(request));
|
|
94
|
+
} catch (error) {
|
|
95
|
+
options.callbacks?.onError?.(error as Error);
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Collect full content from streaming (for onComplete callback)
|
|
102
|
+
*/
|
|
103
|
+
async function collectStreamContent(request: GroqChatRequest): Promise<string> {
|
|
104
|
+
let fullContent = "";
|
|
105
|
+
|
|
106
|
+
for await (const chunk of groqHttpClient.chatCompletionStream(request)) {
|
|
107
|
+
const content = chunk.choices[0]?.delta?.content;
|
|
108
|
+
if (content) {
|
|
109
|
+
fullContent += content;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return fullContent;
|
|
114
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured Text Generation Service
|
|
3
|
+
* Generates structured JSON output from Groq API
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
GroqChatRequest,
|
|
8
|
+
GroqMessage,
|
|
9
|
+
GroqGenerationConfig,
|
|
10
|
+
} from "../../domain/entities";
|
|
11
|
+
import { groqHttpClient } from "./GroqClient";
|
|
12
|
+
import { DEFAULT_MODELS } from "../../domain/entities";
|
|
13
|
+
import { GroqError, GroqErrorType } from "../../domain/entities/error.types";
|
|
14
|
+
|
|
15
|
+
export interface StructuredTextOptions<T> {
|
|
16
|
+
model?: string;
|
|
17
|
+
generationConfig?: GroqGenerationConfig;
|
|
18
|
+
schema?: Record<string, unknown>;
|
|
19
|
+
example?: T;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generate structured JSON output from a prompt
|
|
24
|
+
*/
|
|
25
|
+
export async function structuredText<T = Record<string, unknown>>(
|
|
26
|
+
prompt: string,
|
|
27
|
+
options: StructuredTextOptions<T> = {}
|
|
28
|
+
): Promise<T> {
|
|
29
|
+
const model = options.model || DEFAULT_MODELS.TEXT;
|
|
30
|
+
|
|
31
|
+
let systemPrompt = "You are a helpful assistant that generates valid JSON output.";
|
|
32
|
+
|
|
33
|
+
if (options.schema) {
|
|
34
|
+
systemPrompt += `\n\nResponse must conform to this JSON schema:\n${JSON.stringify(options.schema, null, 2)}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (options.example) {
|
|
38
|
+
systemPrompt += `\n\nExample response format:\n${JSON.stringify(options.example, null, 2)}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
systemPrompt += "\n\nIMPORTANT: Respond ONLY with valid JSON. No markdown, no code blocks, no explanations.";
|
|
42
|
+
|
|
43
|
+
const messages: GroqMessage[] = [
|
|
44
|
+
{
|
|
45
|
+
role: "system",
|
|
46
|
+
content: systemPrompt,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
role: "user",
|
|
50
|
+
content: prompt,
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const request: GroqChatRequest = {
|
|
55
|
+
model,
|
|
56
|
+
messages,
|
|
57
|
+
temperature: options.generationConfig?.temperature || 0.3,
|
|
58
|
+
max_tokens: options.generationConfig?.maxTokens || 2048,
|
|
59
|
+
top_p: options.generationConfig?.topP || 0.9,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const response = await groqHttpClient.chatCompletion(request);
|
|
63
|
+
|
|
64
|
+
let content = response.choices[0]?.message?.content;
|
|
65
|
+
if (!content) {
|
|
66
|
+
throw new GroqError(
|
|
67
|
+
GroqErrorType.UNKNOWN_ERROR,
|
|
68
|
+
"No content generated from Groq API"
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Clean up the response: remove markdown code blocks if present
|
|
73
|
+
content = content.trim();
|
|
74
|
+
if (content.startsWith("```json")) {
|
|
75
|
+
content = content.slice(7);
|
|
76
|
+
} else if (content.startsWith("```")) {
|
|
77
|
+
content = content.slice(3);
|
|
78
|
+
}
|
|
79
|
+
if (content.endsWith("```")) {
|
|
80
|
+
content = content.slice(0, -3);
|
|
81
|
+
}
|
|
82
|
+
content = content.trim();
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(content) as T;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
throw new GroqError(
|
|
88
|
+
GroqErrorType.UNKNOWN_ERROR,
|
|
89
|
+
`Failed to parse JSON response: ${content}`,
|
|
90
|
+
error
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Generate structured JSON output from messages
|
|
97
|
+
*/
|
|
98
|
+
export async function structuredChat<T = Record<string, unknown>>(
|
|
99
|
+
messages: GroqMessage[],
|
|
100
|
+
options: StructuredTextOptions<T> = {}
|
|
101
|
+
): Promise<T> {
|
|
102
|
+
const model = options.model || DEFAULT_MODELS.TEXT;
|
|
103
|
+
|
|
104
|
+
let systemMessage: GroqMessage | null = null;
|
|
105
|
+
|
|
106
|
+
// Check if there's already a system message
|
|
107
|
+
const hasSystemMessage = messages.some((m) => m.role === "system");
|
|
108
|
+
|
|
109
|
+
if (!hasSystemMessage) {
|
|
110
|
+
let systemPrompt = "You are a helpful assistant that generates valid JSON output.";
|
|
111
|
+
|
|
112
|
+
if (options.schema) {
|
|
113
|
+
systemPrompt += `\n\nResponse must conform to this JSON schema:\n${JSON.stringify(options.schema, null, 2)}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (options.example) {
|
|
117
|
+
systemPrompt += `\n\nExample response format:\n${JSON.stringify(options.example, null, 2)}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
systemPrompt += "\n\nIMPORTANT: Respond ONLY with valid JSON. No markdown, no code blocks, no explanations.";
|
|
121
|
+
|
|
122
|
+
systemMessage = {
|
|
123
|
+
role: "system",
|
|
124
|
+
content: systemPrompt,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const requestMessages = systemMessage
|
|
129
|
+
? [systemMessage, ...messages]
|
|
130
|
+
: messages;
|
|
131
|
+
|
|
132
|
+
const request: GroqChatRequest = {
|
|
133
|
+
model,
|
|
134
|
+
messages: requestMessages,
|
|
135
|
+
temperature: options.generationConfig?.temperature || 0.3,
|
|
136
|
+
max_tokens: options.generationConfig?.maxTokens || 2048,
|
|
137
|
+
top_p: options.generationConfig?.topP || 0.9,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const response = await groqHttpClient.chatCompletion(request);
|
|
141
|
+
|
|
142
|
+
let content = response.choices[0]?.message?.content;
|
|
143
|
+
if (!content) {
|
|
144
|
+
throw new GroqError(
|
|
145
|
+
GroqErrorType.UNKNOWN_ERROR,
|
|
146
|
+
"No content generated from Groq API"
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Clean up the response: remove markdown code blocks if present
|
|
151
|
+
content = content.trim();
|
|
152
|
+
if (content.startsWith("```json")) {
|
|
153
|
+
content = content.slice(7);
|
|
154
|
+
} else if (content.startsWith("```")) {
|
|
155
|
+
content = content.slice(3);
|
|
156
|
+
}
|
|
157
|
+
if (content.endsWith("```")) {
|
|
158
|
+
content = content.slice(0, -3);
|
|
159
|
+
}
|
|
160
|
+
content = content.trim();
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
return JSON.parse(content) as T;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
throw new GroqError(
|
|
166
|
+
GroqErrorType.UNKNOWN_ERROR,
|
|
167
|
+
`Failed to parse JSON response: ${content}`,
|
|
168
|
+
error
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text Generation Service
|
|
3
|
+
* Handles basic text generation using Groq API
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
GroqChatRequest,
|
|
8
|
+
GroqChatResponse,
|
|
9
|
+
GroqMessage,
|
|
10
|
+
GroqGenerationConfig,
|
|
11
|
+
} from "../../domain/entities";
|
|
12
|
+
import { groqHttpClient } from "./GroqClient";
|
|
13
|
+
import { DEFAULT_MODELS } from "../../domain/entities";
|
|
14
|
+
import { GroqError, GroqErrorType } from "../../domain/entities/error.types";
|
|
15
|
+
|
|
16
|
+
export interface TextGenerationOptions {
|
|
17
|
+
model?: string;
|
|
18
|
+
generationConfig?: GroqGenerationConfig;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate text from a simple prompt
|
|
23
|
+
*/
|
|
24
|
+
export async function textGeneration(
|
|
25
|
+
prompt: string,
|
|
26
|
+
options: TextGenerationOptions = {}
|
|
27
|
+
): Promise<string> {
|
|
28
|
+
const model = options.model || DEFAULT_MODELS.TEXT;
|
|
29
|
+
|
|
30
|
+
const messages: GroqMessage[] = [
|
|
31
|
+
{
|
|
32
|
+
role: "user",
|
|
33
|
+
content: prompt,
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const request: GroqChatRequest = {
|
|
38
|
+
model,
|
|
39
|
+
messages,
|
|
40
|
+
temperature: options.generationConfig?.temperature || 0.7,
|
|
41
|
+
max_tokens: options.generationConfig?.maxTokens || 1024,
|
|
42
|
+
top_p: options.generationConfig?.topP,
|
|
43
|
+
n: options.generationConfig?.n,
|
|
44
|
+
stop: options.generationConfig?.stop,
|
|
45
|
+
frequency_penalty: options.generationConfig?.frequencyPenalty,
|
|
46
|
+
presence_penalty: options.generationConfig?.presencePenalty,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const response = await groqHttpClient.chatCompletion(request);
|
|
50
|
+
|
|
51
|
+
const content = response.choices[0]?.message?.content;
|
|
52
|
+
if (!content) {
|
|
53
|
+
throw new GroqError(
|
|
54
|
+
GroqErrorType.UNKNOWN_ERROR,
|
|
55
|
+
"No content generated from Groq API"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return content;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Generate text from an array of messages
|
|
64
|
+
*/
|
|
65
|
+
export async function chatGeneration(
|
|
66
|
+
messages: GroqMessage[],
|
|
67
|
+
options: TextGenerationOptions = {}
|
|
68
|
+
): Promise<string> {
|
|
69
|
+
const model = options.model || DEFAULT_MODELS.TEXT;
|
|
70
|
+
|
|
71
|
+
const request: GroqChatRequest = {
|
|
72
|
+
model,
|
|
73
|
+
messages,
|
|
74
|
+
temperature: options.generationConfig?.temperature || 0.7,
|
|
75
|
+
max_tokens: options.generationConfig?.maxTokens || 1024,
|
|
76
|
+
top_p: options.generationConfig?.topP,
|
|
77
|
+
n: options.generationConfig?.n,
|
|
78
|
+
stop: options.generationConfig?.stop,
|
|
79
|
+
frequency_penalty: options.generationConfig?.frequencyPenalty,
|
|
80
|
+
presence_penalty: options.generationConfig?.presencePenalty,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const response = await groqHttpClient.chatCompletion(request);
|
|
84
|
+
|
|
85
|
+
const content = response.choices[0]?.message?.content;
|
|
86
|
+
if (!content) {
|
|
87
|
+
throw new GroqError(
|
|
88
|
+
GroqErrorType.UNKNOWN_ERROR,
|
|
89
|
+
"No content generated from Groq API"
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return content;
|
|
94
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infrastructure Services
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { groqHttpClient } from "./GroqClient";
|
|
6
|
+
export { textGeneration, chatGeneration } from "./TextGeneration";
|
|
7
|
+
export { structuredText, structuredChat } from "./StructuredText";
|
|
8
|
+
export { streaming, streamingChat, type StreamingCallbacks, type StreamingOptions } from "./Streaming";
|
|
9
|
+
export {
|
|
10
|
+
chatSessionService,
|
|
11
|
+
createChatSession,
|
|
12
|
+
sendChatMessage,
|
|
13
|
+
buildChatHistory,
|
|
14
|
+
trimChatHistory,
|
|
15
|
+
type ChatSession,
|
|
16
|
+
type SendChatMessageOptions,
|
|
17
|
+
type ChatSendResult,
|
|
18
|
+
type ChatHistoryMessage,
|
|
19
|
+
} from "./ChatSession";
|