@saltcorn/large-language-model 0.8.10 → 0.9.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.
Files changed (2) hide show
  1. package/generate.js +107 -7
  2. package/package.json +1 -1
package/generate.js CHANGED
@@ -1,4 +1,4 @@
1
- const fetch = require("node-fetch");
1
+ const node_fetch = require("node-fetch");
2
2
  const util = require("util");
3
3
  const exec = util.promisify(require("child_process").exec);
4
4
  const db = require("@saltcorn/data/db");
@@ -207,7 +207,10 @@ const getCompletionOpenAICompatible = async (
207
207
  )
208
208
  body.temperature = 0.7;
209
209
  }
210
-
210
+ if (rest.streamCallback) {
211
+ body.stream = true;
212
+ delete body.streamCallback;
213
+ }
211
214
  if (responses_api) {
212
215
  for (const tool of body.tools || []) {
213
216
  if (tool.type !== "function") continue;
@@ -303,18 +306,115 @@ const getCompletionOpenAICompatible = async (
303
306
  )} to ${chatCompleteEndpoint} headers ${JSON.stringify(headers)}`
304
307
  );
305
308
  if (debugCollector) debugCollector.request = body;
306
-
307
- const rawResponse = await fetch(chatCompleteEndpoint, {
309
+ const reqTimeStart = Date.now();
310
+ const rawResponse = await (global.fetch || node_fetch)(chatCompleteEndpoint, {
308
311
  method: "POST",
309
312
  headers,
310
313
  body: JSON.stringify(body),
311
314
  });
315
+ let streamParts = [];
316
+ let streamToolCalls = null;
317
+
318
+ if (rest.streamCallback) {
319
+ // https://stackoverflow.com/a/75751803/19839414
320
+ // https://stackoverflow.com/a/57664622/19839414
321
+ await new Promise((resolve, reject) => {
322
+ let dataDone = false;
323
+ let stashed = "";
324
+
325
+ const process_stream_data = (value) => {
326
+ const arr = value.split("\n");
327
+ arr.forEach((data) => {
328
+ if (data.length === 0) return; // ignore empty message
329
+ if (data.startsWith(":")) return; // ignore sse comment message
330
+ if (data === "data: [DONE]") {
331
+ dataDone = true;
332
+ resolve();
333
+ return;
334
+ }
335
+ try {
336
+ const json = JSON.parse(stashed + data.substring(6));
337
+ stashed = "";
338
+ console.log(json.choices[0]);
339
+
340
+ // callback
341
+
342
+ //answer store
343
+ if (json.choices?.[0]?.content)
344
+ streamParts.push(json.choices[0].content);
345
+ if (json.choices?.[0]?.delta?.content)
346
+ streamParts.push(json.choices[0].delta.content);
347
+ if (json.choices?.[0]?.delta?.tool_calls) {
348
+ if (!streamToolCalls) streamToolCalls = json.choices?.[0]?.delta;
349
+ else
350
+ json.choices?.[0]?.delta?.tool_calls.forEach((tc, ix) => {
351
+ streamToolCalls.tool_calls[ix].function.arguments +=
352
+ tc.function.arguments;
353
+ });
354
+ }
355
+ rest.streamCallback(json);
356
+ } catch (e) {
357
+ //console.error(e);
358
+ stashed = data.substring(6);
359
+ }
360
+ });
361
+ };
362
+ if (global.fetch) {
363
+ const reader = rawResponse.body
364
+ ?.pipeThrough(new TextDecoderStream())
365
+ .getReader();
366
+ if (!reader) return;
367
+ // eslint-disable-next-line no-constant-condition
368
+ (async () => {
369
+ while (!dataDone) {
370
+ // eslint-disable-next-line no-await-in-loop
371
+
372
+ const { value, done } = await reader.read();
373
+
374
+ if (done) {
375
+ dataDone = true;
376
+ resolve();
377
+ break;
378
+ }
379
+ process_stream_data(value);
380
+ if (dataDone) break;
381
+ }
382
+ })().catch((e) => {
383
+ //console.error(e);
384
+ dataDone = true;
385
+ reject(e);
386
+ });
387
+ } else
388
+ rawResponse.body.on("readable", () => {
389
+ let chunk;
390
+ while (null !== (chunk = rawResponse.body.read())) {
391
+ let value = chunk.toString();
392
+ process_stream_data(value);
393
+ if (dataDone) break;
394
+ }
395
+ });
396
+ });
397
+ if (debugCollector) {
398
+ //TODO get the full response
399
+ if (streamToolCalls) debugCollector.response = streamToolCalls;
400
+ debugCollector.response_time_ms = Date.now() - reqTimeStart;
401
+ }
402
+ return streamToolCalls
403
+ ? {
404
+ content: streamParts.join(""),
405
+ tool_calls: streamToolCalls.tool_calls,
406
+ }
407
+ : streamParts.join("");
408
+ }
312
409
  const results = await rawResponse.json();
313
410
  //console.log("results", results);
314
411
  if (debugResult)
315
412
  console.log("OpenAI response", JSON.stringify(results, null, 2));
316
413
  else getState().log(6, `OpenAI response ${JSON.stringify(results)}`);
317
- if (debugCollector) debugCollector.response = results;
414
+ if (debugCollector) {
415
+ debugCollector.response = results;
416
+ debugCollector.response_time_ms = Date.now() - reqTimeStart;
417
+ }
318
418
 
319
419
  if (results.error) throw new Error(`OpenAI error: ${results.error.message}`);
320
420
  if (responses_api) {
@@ -395,7 +495,7 @@ const getImageGenOpenAICompatible = async (
395
495
  if (debugResult) console.log("OpenAI image request", imageEndpoint, body);
396
496
  if (debugCollector) debugCollector.request = body;
397
497
 
398
- const rawResponse = await fetch(imageEndpoint, {
498
+ const rawResponse = await (global.fetch || node_fetch)(imageEndpoint, {
399
499
  method: "POST",
400
500
  headers,
401
501
  body: JSON.stringify(body),
@@ -424,7 +524,7 @@ const getEmbeddingOpenAICompatible = async (
424
524
  input: prompt,
425
525
  };
426
526
 
427
- const rawResponse = await fetch(embeddingsEndpoint, {
527
+ const rawResponse = await (global.fetch || node_fetch)(embeddingsEndpoint, {
428
528
  method: "POST",
429
529
  headers,
430
530
  body: JSON.stringify(body),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/large-language-model",
3
- "version": "0.8.10",
3
+ "version": "0.9.0",
4
4
  "description": "Large language models and functionality for Saltcorn",
5
5
  "main": "index.js",
6
6
  "dependencies": {