@yetter/client 0.0.3 → 0.0.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/README.md +15 -1
- package/dist/api.js +4 -4
- package/dist/client.d.ts +4 -1
- package/dist/client.js +41 -21
- package/dist/types.d.ts +1 -0
- package/examples/stream.ts +2 -1
- package/package.json +1 -1
- package/src/api.ts +4 -4
- package/src/client.ts +43 -21
- package/src/types.ts +1 -0
- package/stress.ts +0 -135
package/README.md
CHANGED
|
@@ -10,12 +10,26 @@ npm install @yetter/client
|
|
|
10
10
|
|
|
11
11
|
## Authentication
|
|
12
12
|
|
|
13
|
-
The client requires a Yetter API key for authentication. Ensure you have the `YTR_API_KEY` environment variable set:
|
|
13
|
+
The client requires a Yetter API key for authentication. Ensure you have the `YTR_API_KEY` or `REACT_APP_YTR_API_KEY` environment variable set:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
export YTR_API_KEY="your_api_key_here"
|
|
17
|
+
export REACT_APP_YTR_API_KEY="your_api_key_here"
|
|
17
18
|
```
|
|
18
19
|
|
|
20
|
+
Alternatively, you can configure the API key (and optionally the API endpoint) programmatically using the `yetter.configure()` method. This is useful if you prefer not to use environment variables or need to set the key at runtime.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { yetter } from "@yetter/client";
|
|
24
|
+
|
|
25
|
+
yetter.configure({
|
|
26
|
+
apiKey: "your_api_key_here",
|
|
27
|
+
// endpoint: "https://custom.api.yetter.ai" // if you need to override the default endpoint
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If `yetter.configure()` is used, it will override any API key found in environment variables for subsequent API calls. If neither environment variables are set nor `yetter.configure()` is called with an API key, an error will be thrown when attempting to make an API call.
|
|
32
|
+
|
|
19
33
|
## Core Functionalities
|
|
20
34
|
|
|
21
35
|
The client is available via the `yetter` object imported from `yetter-js` (or the relevant path to `client.js`/`client.ts` if used directly).
|
package/dist/api.js
CHANGED
|
@@ -16,7 +16,7 @@ export class YetterImageClient {
|
|
|
16
16
|
method: "POST",
|
|
17
17
|
headers: {
|
|
18
18
|
"Content-Type": "application/json",
|
|
19
|
-
Authorization:
|
|
19
|
+
Authorization: this.apiKey,
|
|
20
20
|
},
|
|
21
21
|
body: JSON.stringify(body),
|
|
22
22
|
});
|
|
@@ -35,7 +35,7 @@ export class YetterImageClient {
|
|
|
35
35
|
method: "GET",
|
|
36
36
|
headers: {
|
|
37
37
|
"Content-Type": "application/json",
|
|
38
|
-
Authorization:
|
|
38
|
+
Authorization: this.apiKey,
|
|
39
39
|
},
|
|
40
40
|
});
|
|
41
41
|
if (!res.ok) {
|
|
@@ -49,7 +49,7 @@ export class YetterImageClient {
|
|
|
49
49
|
method: "PUT",
|
|
50
50
|
headers: {
|
|
51
51
|
"Content-Type": "application/json",
|
|
52
|
-
Authorization:
|
|
52
|
+
Authorization: this.apiKey,
|
|
53
53
|
},
|
|
54
54
|
});
|
|
55
55
|
if (!res.ok) {
|
|
@@ -63,7 +63,7 @@ export class YetterImageClient {
|
|
|
63
63
|
method: "GET",
|
|
64
64
|
headers: {
|
|
65
65
|
"Content-Type": "application/json",
|
|
66
|
-
Authorization:
|
|
66
|
+
Authorization: this.apiKey,
|
|
67
67
|
},
|
|
68
68
|
});
|
|
69
69
|
if (!res.ok) {
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { GetResponseResponse, SubscribeOptions, GenerateImageResponse, SubmitQueueOptions, GetResultOptions, GetResultResponse, StatusOptions, StatusResponse, StreamOptions, YetterStream } from "./types.js";
|
|
1
|
+
import { ClientOptions, GetResponseResponse, SubscribeOptions, GenerateImageResponse, SubmitQueueOptions, GetResultOptions, GetResultResponse, StatusOptions, StatusResponse, StreamOptions, YetterStream } from "./types.js";
|
|
2
2
|
export declare class yetter {
|
|
3
|
+
private static apiKey;
|
|
4
|
+
private static endpoint;
|
|
5
|
+
static configure(options: ClientOptions): void;
|
|
3
6
|
static subscribe(model: string, options: SubscribeOptions): Promise<GetResponseResponse>;
|
|
4
7
|
static queue: {
|
|
5
8
|
submit: (model: string, options: SubmitQueueOptions) => Promise<GenerateImageResponse>;
|
package/dist/client.js
CHANGED
|
@@ -2,13 +2,27 @@ var _a;
|
|
|
2
2
|
import { YetterImageClient } from "./api.js";
|
|
3
3
|
import { EventSourcePolyfill } from 'event-source-polyfill';
|
|
4
4
|
export class yetter {
|
|
5
|
+
static configure(options) {
|
|
6
|
+
if (options.apiKey) {
|
|
7
|
+
if (options.is_bearer) {
|
|
8
|
+
_a.apiKey = 'Bearer ' + options.apiKey;
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
_a.apiKey = 'Key ' + options.apiKey;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
if (options.endpoint) {
|
|
15
|
+
_a.endpoint = options.endpoint;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
5
18
|
static async subscribe(model, options) {
|
|
6
19
|
var _b;
|
|
7
|
-
if (!
|
|
8
|
-
throw new Error("
|
|
20
|
+
if (!_a.apiKey) {
|
|
21
|
+
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
9
22
|
}
|
|
10
23
|
const client = new YetterImageClient({
|
|
11
|
-
apiKey:
|
|
24
|
+
apiKey: _a.apiKey,
|
|
25
|
+
endpoint: _a.endpoint
|
|
12
26
|
});
|
|
13
27
|
const generateResponse = await client.generateImage({
|
|
14
28
|
model: model,
|
|
@@ -55,20 +69,21 @@ export class yetter {
|
|
|
55
69
|
return finalResponse;
|
|
56
70
|
}
|
|
57
71
|
static async stream(model, options) {
|
|
58
|
-
if (!
|
|
59
|
-
throw new Error("
|
|
72
|
+
if (!_a.apiKey) {
|
|
73
|
+
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
60
74
|
}
|
|
61
|
-
const
|
|
62
|
-
apiKey:
|
|
75
|
+
const client = new YetterImageClient({
|
|
76
|
+
apiKey: _a.apiKey,
|
|
77
|
+
endpoint: _a.endpoint
|
|
63
78
|
});
|
|
64
|
-
const initialApiResponse = await
|
|
79
|
+
const initialApiResponse = await client.generateImage({
|
|
65
80
|
model: model,
|
|
66
81
|
...options.input,
|
|
67
82
|
});
|
|
68
83
|
const requestId = initialApiResponse.request_id;
|
|
69
84
|
const responseUrl = initialApiResponse.response_url;
|
|
70
85
|
const cancelUrl = initialApiResponse.cancel_url;
|
|
71
|
-
const sseStreamUrl = `${
|
|
86
|
+
const sseStreamUrl = `${client.getApiEndpoint()}/${model}/requests/${requestId}/status/stream`;
|
|
72
87
|
let eventSource;
|
|
73
88
|
let streamEnded = false;
|
|
74
89
|
// Setup the promise for the done() method
|
|
@@ -97,7 +112,7 @@ export class yetter {
|
|
|
97
112
|
// Check for terminal events to resolve/reject the donePromise
|
|
98
113
|
if (event.status === "COMPLETED") {
|
|
99
114
|
streamEnded = true;
|
|
100
|
-
|
|
115
|
+
client.getResponse({ url: responseUrl })
|
|
101
116
|
.then(resolveDonePromise)
|
|
102
117
|
.catch(rejectDonePromise)
|
|
103
118
|
.finally(() => this.close());
|
|
@@ -154,7 +169,7 @@ export class yetter {
|
|
|
154
169
|
}
|
|
155
170
|
};
|
|
156
171
|
eventSource = new EventSourcePolyfill(sseStreamUrl, {
|
|
157
|
-
headers: { 'Authorization': `${
|
|
172
|
+
headers: { 'Authorization': `${_a.apiKey}` }
|
|
158
173
|
});
|
|
159
174
|
eventSource.onopen = (event) => {
|
|
160
175
|
console.log("SSE Connection Opened:", event);
|
|
@@ -198,7 +213,7 @@ export class yetter {
|
|
|
198
213
|
cancel: async () => {
|
|
199
214
|
controller.close();
|
|
200
215
|
try {
|
|
201
|
-
await
|
|
216
|
+
await client.cancel({ url: cancelUrl });
|
|
202
217
|
console.log(`Stream for ${requestId} - underlying request cancelled.`);
|
|
203
218
|
}
|
|
204
219
|
catch (e) {
|
|
@@ -214,13 +229,16 @@ export class yetter {
|
|
|
214
229
|
}
|
|
215
230
|
}
|
|
216
231
|
_a = yetter;
|
|
232
|
+
yetter.apiKey = 'Key ' + (process.env.YTR_API_KEY || process.env.REACT_APP_YTR_API_KEY || "");
|
|
233
|
+
yetter.endpoint = "https://api.yetter.ai";
|
|
217
234
|
yetter.queue = {
|
|
218
235
|
submit: async (model, options) => {
|
|
219
|
-
if (!
|
|
220
|
-
throw new Error("
|
|
236
|
+
if (!_a.apiKey) {
|
|
237
|
+
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
221
238
|
}
|
|
222
239
|
const client = new YetterImageClient({
|
|
223
|
-
apiKey:
|
|
240
|
+
apiKey: _a.apiKey,
|
|
241
|
+
endpoint: _a.endpoint
|
|
224
242
|
});
|
|
225
243
|
const generateResponse = await client.generateImage({
|
|
226
244
|
model: model,
|
|
@@ -229,11 +247,12 @@ yetter.queue = {
|
|
|
229
247
|
return generateResponse;
|
|
230
248
|
},
|
|
231
249
|
status: async (model, options) => {
|
|
232
|
-
if (!
|
|
233
|
-
throw new Error("
|
|
250
|
+
if (!_a.apiKey) {
|
|
251
|
+
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
234
252
|
}
|
|
235
253
|
const client = new YetterImageClient({
|
|
236
|
-
apiKey:
|
|
254
|
+
apiKey: _a.apiKey,
|
|
255
|
+
endpoint: _a.endpoint
|
|
237
256
|
});
|
|
238
257
|
const endpoint = client.getApiEndpoint();
|
|
239
258
|
const statusUrl = `${endpoint}/${model}/requests/${options.requestId}/status`;
|
|
@@ -244,11 +263,12 @@ yetter.queue = {
|
|
|
244
263
|
};
|
|
245
264
|
},
|
|
246
265
|
result: async (model, options) => {
|
|
247
|
-
if (!
|
|
248
|
-
throw new Error("
|
|
266
|
+
if (!_a.apiKey) {
|
|
267
|
+
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
249
268
|
}
|
|
250
269
|
const client = new YetterImageClient({
|
|
251
|
-
apiKey:
|
|
270
|
+
apiKey: _a.apiKey,
|
|
271
|
+
endpoint: _a.endpoint
|
|
252
272
|
});
|
|
253
273
|
const endpoint = client.getApiEndpoint();
|
|
254
274
|
const responseUrl = `${endpoint}/${model}/requests/${options.requestId}`;
|
package/dist/types.d.ts
CHANGED
package/examples/stream.ts
CHANGED
|
@@ -9,6 +9,7 @@ async function main() {
|
|
|
9
9
|
const streamInstance = await yetter.stream(model, {
|
|
10
10
|
input: {
|
|
11
11
|
prompt: "a bioluminescent forest at night, fantasy art",
|
|
12
|
+
seed: 42
|
|
12
13
|
// You can add other parameters like seed or num_inference_steps here
|
|
13
14
|
},
|
|
14
15
|
});
|
|
@@ -34,7 +35,7 @@ async function main() {
|
|
|
34
35
|
console.log("Generated Images:", result.images);
|
|
35
36
|
} else {
|
|
36
37
|
console.log("No images in final result.");
|
|
37
|
-
}
|
|
38
|
+
}''
|
|
38
39
|
console.log("Original Prompt:", result.prompt);
|
|
39
40
|
|
|
40
41
|
} catch (err: any) {
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -35,7 +35,7 @@ export class YetterImageClient {
|
|
|
35
35
|
method: "POST",
|
|
36
36
|
headers: {
|
|
37
37
|
"Content-Type": "application/json",
|
|
38
|
-
Authorization:
|
|
38
|
+
Authorization: this.apiKey,
|
|
39
39
|
},
|
|
40
40
|
body: JSON.stringify(body),
|
|
41
41
|
});
|
|
@@ -58,7 +58,7 @@ export class YetterImageClient {
|
|
|
58
58
|
method: "GET",
|
|
59
59
|
headers: {
|
|
60
60
|
"Content-Type": "application/json",
|
|
61
|
-
Authorization:
|
|
61
|
+
Authorization: this.apiKey,
|
|
62
62
|
},
|
|
63
63
|
});
|
|
64
64
|
|
|
@@ -75,7 +75,7 @@ export class YetterImageClient {
|
|
|
75
75
|
method: "PUT",
|
|
76
76
|
headers: {
|
|
77
77
|
"Content-Type": "application/json",
|
|
78
|
-
Authorization:
|
|
78
|
+
Authorization: this.apiKey,
|
|
79
79
|
},
|
|
80
80
|
});
|
|
81
81
|
|
|
@@ -92,7 +92,7 @@ export class YetterImageClient {
|
|
|
92
92
|
method: "GET",
|
|
93
93
|
headers: {
|
|
94
94
|
"Content-Type": "application/json",
|
|
95
|
-
Authorization:
|
|
95
|
+
Authorization: this.apiKey,
|
|
96
96
|
},
|
|
97
97
|
});
|
|
98
98
|
|
package/src/client.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { YetterImageClient } from "./api.js";
|
|
2
2
|
import { EventSourcePolyfill } from 'event-source-polyfill';
|
|
3
3
|
import {
|
|
4
|
+
ClientOptions,
|
|
4
5
|
GetStatusResponse,
|
|
5
6
|
GetResponseResponse,
|
|
6
7
|
SubscribeOptions,
|
|
@@ -16,15 +17,32 @@ import {
|
|
|
16
17
|
} from "./types.js";
|
|
17
18
|
|
|
18
19
|
export class yetter {
|
|
20
|
+
private static apiKey: string = 'Key ' + (process.env.YTR_API_KEY || process.env.REACT_APP_YTR_API_KEY || "");
|
|
21
|
+
private static endpoint: string = "https://api.yetter.ai";
|
|
22
|
+
|
|
23
|
+
public static configure(options: ClientOptions) {
|
|
24
|
+
if (options.apiKey) {
|
|
25
|
+
if (options.is_bearer) {
|
|
26
|
+
yetter.apiKey = 'Bearer ' + options.apiKey;
|
|
27
|
+
} else {
|
|
28
|
+
yetter.apiKey = 'Key ' + options.apiKey;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (options.endpoint) {
|
|
32
|
+
yetter.endpoint = options.endpoint;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
19
36
|
public static async subscribe(
|
|
20
37
|
model: string,
|
|
21
38
|
options: SubscribeOptions
|
|
22
39
|
): Promise<GetResponseResponse> {
|
|
23
|
-
if (!
|
|
24
|
-
throw new Error("
|
|
40
|
+
if (!yetter.apiKey) {
|
|
41
|
+
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
25
42
|
}
|
|
26
43
|
const client = new YetterImageClient({
|
|
27
|
-
apiKey:
|
|
44
|
+
apiKey: yetter.apiKey,
|
|
45
|
+
endpoint: yetter.endpoint
|
|
28
46
|
});
|
|
29
47
|
|
|
30
48
|
const generateResponse = await client.generateImage({
|
|
@@ -83,11 +101,12 @@ export class yetter {
|
|
|
83
101
|
model: string,
|
|
84
102
|
options: SubmitQueueOptions
|
|
85
103
|
): Promise<GenerateImageResponse> => {
|
|
86
|
-
if (!
|
|
87
|
-
throw new Error("
|
|
104
|
+
if (!yetter.apiKey) {
|
|
105
|
+
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
88
106
|
}
|
|
89
107
|
const client = new YetterImageClient({
|
|
90
|
-
apiKey:
|
|
108
|
+
apiKey: yetter.apiKey,
|
|
109
|
+
endpoint: yetter.endpoint
|
|
91
110
|
});
|
|
92
111
|
|
|
93
112
|
const generateResponse = await client.generateImage({
|
|
@@ -102,11 +121,12 @@ export class yetter {
|
|
|
102
121
|
model: string,
|
|
103
122
|
options: StatusOptions
|
|
104
123
|
): Promise<StatusResponse> => {
|
|
105
|
-
if (!
|
|
106
|
-
throw new Error("
|
|
124
|
+
if (!yetter.apiKey) {
|
|
125
|
+
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
107
126
|
}
|
|
108
127
|
const client = new YetterImageClient({
|
|
109
|
-
apiKey:
|
|
128
|
+
apiKey: yetter.apiKey,
|
|
129
|
+
endpoint: yetter.endpoint
|
|
110
130
|
});
|
|
111
131
|
|
|
112
132
|
const endpoint = client.getApiEndpoint();
|
|
@@ -125,11 +145,12 @@ export class yetter {
|
|
|
125
145
|
model: string,
|
|
126
146
|
options: GetResultOptions
|
|
127
147
|
): Promise<GetResultResponse> => {
|
|
128
|
-
if (!
|
|
129
|
-
throw new Error("
|
|
148
|
+
if (!yetter.apiKey) {
|
|
149
|
+
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
130
150
|
}
|
|
131
151
|
const client = new YetterImageClient({
|
|
132
|
-
apiKey:
|
|
152
|
+
apiKey: yetter.apiKey,
|
|
153
|
+
endpoint: yetter.endpoint
|
|
133
154
|
});
|
|
134
155
|
|
|
135
156
|
const endpoint = client.getApiEndpoint();
|
|
@@ -149,14 +170,15 @@ export class yetter {
|
|
|
149
170
|
model: string,
|
|
150
171
|
options: StreamOptions
|
|
151
172
|
): Promise<YetterStream> {
|
|
152
|
-
if (!
|
|
153
|
-
throw new Error("
|
|
173
|
+
if (!yetter.apiKey) {
|
|
174
|
+
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
154
175
|
}
|
|
155
|
-
const
|
|
156
|
-
apiKey:
|
|
176
|
+
const client = new YetterImageClient({
|
|
177
|
+
apiKey: yetter.apiKey,
|
|
178
|
+
endpoint: yetter.endpoint
|
|
157
179
|
});
|
|
158
180
|
|
|
159
|
-
const initialApiResponse = await
|
|
181
|
+
const initialApiResponse = await client.generateImage({
|
|
160
182
|
model: model,
|
|
161
183
|
...options.input,
|
|
162
184
|
});
|
|
@@ -164,7 +186,7 @@ export class yetter {
|
|
|
164
186
|
const requestId = initialApiResponse.request_id;
|
|
165
187
|
const responseUrl = initialApiResponse.response_url;
|
|
166
188
|
const cancelUrl = initialApiResponse.cancel_url;
|
|
167
|
-
const sseStreamUrl = `${
|
|
189
|
+
const sseStreamUrl = `${client.getApiEndpoint()}/${model}/requests/${requestId}/status/stream`;
|
|
168
190
|
|
|
169
191
|
let eventSource: EventSource;
|
|
170
192
|
let streamEnded = false;
|
|
@@ -193,7 +215,7 @@ export class yetter {
|
|
|
193
215
|
// Check for terminal events to resolve/reject the donePromise
|
|
194
216
|
if (event.status === "COMPLETED") {
|
|
195
217
|
streamEnded = true;
|
|
196
|
-
|
|
218
|
+
client.getResponse({ url: responseUrl })
|
|
197
219
|
.then(resolveDonePromise)
|
|
198
220
|
.catch(rejectDonePromise)
|
|
199
221
|
.finally(() => this.close());
|
|
@@ -249,7 +271,7 @@ export class yetter {
|
|
|
249
271
|
};
|
|
250
272
|
|
|
251
273
|
eventSource = new EventSourcePolyfill(sseStreamUrl, {
|
|
252
|
-
headers: { 'Authorization': `${
|
|
274
|
+
headers: { 'Authorization': `${yetter.apiKey}` }
|
|
253
275
|
} as any);
|
|
254
276
|
|
|
255
277
|
eventSource.onopen = (event: Event) => {
|
|
@@ -296,7 +318,7 @@ export class yetter {
|
|
|
296
318
|
cancel: async () => {
|
|
297
319
|
controller.close();
|
|
298
320
|
try {
|
|
299
|
-
await
|
|
321
|
+
await client.cancel({ url: cancelUrl });
|
|
300
322
|
console.log(`Stream for ${requestId} - underlying request cancelled.`);
|
|
301
323
|
} catch (e: any) {
|
|
302
324
|
console.error(`Error cancelling underlying request for stream ${requestId}:`, e.message);
|
package/src/types.ts
CHANGED
package/stress.ts
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { yetter } from "../src/client.js";
|
|
2
|
-
|
|
3
|
-
const CONCURRENCY_LEVEL = 5; // Number of streams to run in parallel
|
|
4
|
-
const MODEL_NAME = "ytr-ai/flux/dev"; // Model to use for streaming
|
|
5
|
-
|
|
6
|
-
let streamCounter = 0;
|
|
7
|
-
const activePromises: Promise<void>[] = [];
|
|
8
|
-
let shuttingDown = false;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Runs a single stream, measures latency, and logs results.
|
|
12
|
-
* @param id - A unique identifier for the stream.
|
|
13
|
-
*/
|
|
14
|
-
async function runStream(id: number): Promise<void> {
|
|
15
|
-
const startTime = Date.now();
|
|
16
|
-
let streamRequestId = "";
|
|
17
|
-
|
|
18
|
-
console.log(`[${new Date().toLocaleTimeString()}] [Stream ${id}] Starting...`);
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const streamInstance = await yetter.stream(MODEL_NAME, {
|
|
22
|
-
input: {
|
|
23
|
-
prompt: `A beautiful landscape painting, style of Van Gogh, stream ${id}`,
|
|
24
|
-
// You can add other parameters like seed or num_inference_steps here
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
streamRequestId = streamInstance.getRequestId();
|
|
28
|
-
console.log(`[${new Date().toLocaleTimeString()}] [Stream ${id}] Initiated. Request ID: ${streamRequestId}`);
|
|
29
|
-
|
|
30
|
-
// We are primarily interested in the latency of done(), so event iteration can be minimal or skipped.
|
|
31
|
-
// for await (const event of streamInstance) {
|
|
32
|
-
// console.log(`[${new Date().toLocaleTimeString()}][STREAM EVENT - ${streamRequestId}] Status: ${event.status}, QPos: ${event.queue_position}`);
|
|
33
|
-
// }
|
|
34
|
-
|
|
35
|
-
// Wait for the final result from the done() method
|
|
36
|
-
await streamInstance.done();
|
|
37
|
-
const endTime = Date.now();
|
|
38
|
-
const latency = endTime - startTime;
|
|
39
|
-
console.log(`[${new Date().toLocaleTimeString()}] [Stream ${id}] Finished. Request ID: ${streamRequestId}. Latency: ${latency}ms`);
|
|
40
|
-
|
|
41
|
-
} catch (err: any) {
|
|
42
|
-
const endTime = Date.now();
|
|
43
|
-
const latency = endTime - startTime; // Still record latency up to the point of failure
|
|
44
|
-
console.error(`[${new Date().toLocaleTimeString()}] [Stream ${id}] Failed. Request ID: ${streamRequestId || 'UNKNOWN'}. Latency: ${latency}ms. Error: ${err.message || err}`);
|
|
45
|
-
// Optionally, rethrow or handle more specifically if needed
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Launches a new stream if concurrency limit is not reached and not shutting down.
|
|
51
|
-
* Manages the activePromises array.
|
|
52
|
-
*/
|
|
53
|
-
async function launchStreamIfNotBusy(): Promise<void> {
|
|
54
|
-
if (shuttingDown || activePromises.length >= CONCURRENCY_LEVEL) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
streamCounter++;
|
|
59
|
-
const currentStreamId = streamCounter;
|
|
60
|
-
|
|
61
|
-
console.log(`[${new Date().toLocaleTimeString()}] Launching stream ${currentStreamId}. Active: ${activePromises.length}/${CONCURRENCY_LEVEL}`);
|
|
62
|
-
|
|
63
|
-
const streamTask = runStream(currentStreamId);
|
|
64
|
-
activePromises.push(streamTask);
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
await streamTask;
|
|
68
|
-
} catch (e) {
|
|
69
|
-
// Errors are logged within runStream
|
|
70
|
-
} finally {
|
|
71
|
-
const index = activePromises.indexOf(streamTask);
|
|
72
|
-
if (index > -1) {
|
|
73
|
-
activePromises.splice(index, 1);
|
|
74
|
-
}
|
|
75
|
-
console.log(`[${new Date().toLocaleTimeString()}] Stream ${currentStreamId} completed. Active: ${activePromises.length}/${CONCURRENCY_LEVEL}`);
|
|
76
|
-
// After a stream finishes, try to fill its slot immediately
|
|
77
|
-
if (!shuttingDown) {
|
|
78
|
-
launchStreamIfNotBusy(); // Non-blocking call to fill the slot
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Fills available slots up to the CONCURRENCY_LEVEL.
|
|
85
|
-
*/
|
|
86
|
-
function fillSlots() {
|
|
87
|
-
while (!shuttingDown && activePromises.length < CONCURRENCY_LEVEL) {
|
|
88
|
-
// launchStreamIfNotBusy is async but we don't await it here
|
|
89
|
-
// as we want to launch multiple streams in parallel.
|
|
90
|
-
// It handles its own addition to activePromises.
|
|
91
|
-
launchStreamIfNotBusy();
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Main function to run the stress test.
|
|
97
|
-
*/
|
|
98
|
-
async function mainStressTest() {
|
|
99
|
-
console.log("--- Starting Yetter Stream Stress Test ---");
|
|
100
|
-
console.log(`Concurrency Level (N): ${CONCURRENCY_LEVEL}`);
|
|
101
|
-
console.log(`Target Model: ${MODEL_NAME}`);
|
|
102
|
-
console.log("Press Ctrl+C to stop gracefully (will finish active streams).");
|
|
103
|
-
|
|
104
|
-
process.on('SIGINT', async () => {
|
|
105
|
-
console.log('\nSIGINT received. Gracefully shutting down...');
|
|
106
|
-
shuttingDown = true;
|
|
107
|
-
console.log(`No new streams will be launched. Waiting for ${activePromises.length} active stream(s) to complete...`);
|
|
108
|
-
// The main loop will exit once activePromises is empty after shuttingDown is true.
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// Initial fill
|
|
112
|
-
fillSlots();
|
|
113
|
-
|
|
114
|
-
// Keep the script running and responsive to shutdown, also periodically check to fill slots
|
|
115
|
-
// in case some mechanism external to stream completion is needed (though launchStreamIfNotBusy's finally block should cover it)
|
|
116
|
-
while (true) {
|
|
117
|
-
if (shuttingDown && activePromises.length === 0) {
|
|
118
|
-
break; // Exit condition: shutting down and all streams are done.
|
|
119
|
-
}
|
|
120
|
-
// Periodically try to fill slots, mainly as a fallback or if initial fills didn't max out.
|
|
121
|
-
// The primary mechanism for refilling is within launchStreamIfNotBusy's finally block.
|
|
122
|
-
if (!shuttingDown) {
|
|
123
|
-
fillSlots();
|
|
124
|
-
}
|
|
125
|
-
await new Promise(resolve => setTimeout(resolve, 200)); // Interval for the main loop check
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
console.log('--- Stress Test Finished ---');
|
|
129
|
-
console.log(`Total streams launched during the session: ${streamCounter}`);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
mainStressTest().catch(err => {
|
|
133
|
-
console.error("Unhandled error in mainStressTest:", err);
|
|
134
|
-
process.exit(1);
|
|
135
|
-
});
|