@saltcorn/large-language-model 0.8.4 → 0.8.6
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/generate.js +62 -1
- package/index.js +161 -3
- package/package.json +1 -1
package/generate.js
CHANGED
|
@@ -78,6 +78,23 @@ const getEmbedding = async (config, opts) => {
|
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
+
const getImageGeneration = async (config, opts) => {
|
|
82
|
+
switch (config.backend) {
|
|
83
|
+
case "OpenAI":
|
|
84
|
+
return await getImageGenOpenAICompatible(
|
|
85
|
+
{
|
|
86
|
+
imageEndpoint: "https://api.openai.com/v1/images/generations",
|
|
87
|
+
bearer: opts?.api_key || opts?.bearer || config.api_key,
|
|
88
|
+
model: opts?.model || config.model,
|
|
89
|
+
responses_api: config.responses_api,
|
|
90
|
+
},
|
|
91
|
+
opts
|
|
92
|
+
);
|
|
93
|
+
default:
|
|
94
|
+
throw new Error("Image generation not implemented for this backend");
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
81
98
|
const getCompletion = async (config, opts) => {
|
|
82
99
|
switch (config.backend) {
|
|
83
100
|
case "OpenAI":
|
|
@@ -318,6 +335,50 @@ const getCompletionOpenAICompatible = async (
|
|
|
318
335
|
|
|
319
336
|
const emptyToUndefined = (xs) => (xs.length ? xs : undefined);
|
|
320
337
|
|
|
338
|
+
const getImageGenOpenAICompatible = async (
|
|
339
|
+
config,
|
|
340
|
+
{
|
|
341
|
+
prompt,
|
|
342
|
+
model,
|
|
343
|
+
debugResult,
|
|
344
|
+
size,
|
|
345
|
+
quality,
|
|
346
|
+
n,
|
|
347
|
+
output_format,
|
|
348
|
+
response_format,
|
|
349
|
+
}
|
|
350
|
+
) => {
|
|
351
|
+
const { imageEndpoint, bearer, apiKey, image_model } = config;
|
|
352
|
+
const headers = {
|
|
353
|
+
"Content-Type": "application/json",
|
|
354
|
+
Accept: "application/json",
|
|
355
|
+
};
|
|
356
|
+
if (bearer) headers.Authorization = "Bearer " + bearer;
|
|
357
|
+
if (apiKey) headers["api-key"] = apiKey;
|
|
358
|
+
const body = {
|
|
359
|
+
//prompt: "How are you?",
|
|
360
|
+
model: model || image_model || "gpt-image-1",
|
|
361
|
+
prompt,
|
|
362
|
+
size: size || "1024x1024",
|
|
363
|
+
n: n || 1,
|
|
364
|
+
};
|
|
365
|
+
if (quality) body.quality = quality;
|
|
366
|
+
if (output_format) body.output_format = output_format;
|
|
367
|
+
if (response_format) body.response_format = response_format;
|
|
368
|
+
if (n) body.n = n;
|
|
369
|
+
if (debugResult) console.log("OpenAI image request", imageEndpoint, body);
|
|
370
|
+
|
|
371
|
+
const rawResponse = await fetch(imageEndpoint, {
|
|
372
|
+
method: "POST",
|
|
373
|
+
headers,
|
|
374
|
+
body: JSON.stringify(body),
|
|
375
|
+
});
|
|
376
|
+
const results = await rawResponse.json();
|
|
377
|
+
if (debugResult) console.log("OpenAI image response", results);
|
|
378
|
+
if (results.error) throw new Error(`OpenAI error: ${results.error.message}`);
|
|
379
|
+
return results?.data?.[0];
|
|
380
|
+
};
|
|
381
|
+
|
|
321
382
|
const getEmbeddingOpenAICompatible = async (
|
|
322
383
|
config,
|
|
323
384
|
{ prompt, model, debugResult }
|
|
@@ -518,4 +579,4 @@ const getEmbeddingGoogleVertex = async (config, opts, oauth2Client) => {
|
|
|
518
579
|
return embeddings;
|
|
519
580
|
};
|
|
520
581
|
|
|
521
|
-
module.exports = { getCompletion, getEmbedding };
|
|
582
|
+
module.exports = { getCompletion, getEmbedding, getImageGeneration };
|
package/index.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
const Workflow = require("@saltcorn/data/models/workflow");
|
|
2
2
|
const Form = require("@saltcorn/data/models/form");
|
|
3
|
+
const File = require("@saltcorn/data/models/file");
|
|
4
|
+
const User = require("@saltcorn/data/models/user");
|
|
3
5
|
const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
|
|
4
6
|
const Plugin = require("@saltcorn/data/models/plugin");
|
|
5
7
|
const { domReady } = require("@saltcorn/markup/tags");
|
|
6
8
|
const db = require("@saltcorn/data/db");
|
|
7
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
getCompletion,
|
|
11
|
+
getEmbedding,
|
|
12
|
+
getImageGeneration,
|
|
13
|
+
} = require("./generate");
|
|
8
14
|
const { OPENAI_MODELS } = require("./constants.js");
|
|
9
15
|
const { eval_expression } = require("@saltcorn/data/models/expression");
|
|
10
16
|
const { interpolate } = require("@saltcorn/data/utils");
|
|
@@ -126,7 +132,17 @@ ${domReady(`
|
|
|
126
132
|
],
|
|
127
133
|
},
|
|
128
134
|
},
|
|
129
|
-
|
|
135
|
+
{
|
|
136
|
+
name: "image_model",
|
|
137
|
+
label: "Image model", //gpt-3.5-turbo
|
|
138
|
+
type: "String",
|
|
139
|
+
required: true,
|
|
140
|
+
showIf: { backend: "OpenAI" },
|
|
141
|
+
attributes: {
|
|
142
|
+
options: ["gpt-image-1", "dall-e-2", "dall-e-3"],
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
130
146
|
name: "client_id",
|
|
131
147
|
label: "Client ID",
|
|
132
148
|
sublabel: "OAuth2 client ID from your Google Cloud account",
|
|
@@ -219,7 +235,7 @@ ${domReady(`
|
|
|
219
235
|
showIf: { backend: "Google Vertex AI" },
|
|
220
236
|
default: "us-central1",
|
|
221
237
|
},
|
|
222
|
-
|
|
238
|
+
|
|
223
239
|
{
|
|
224
240
|
name: "bearer_auth",
|
|
225
241
|
label: "Bearer Auth",
|
|
@@ -318,6 +334,15 @@ const functions = (config) => {
|
|
|
318
334
|
description: "Generate text with GPT",
|
|
319
335
|
arguments: [{ name: "prompt", type: "String" }],
|
|
320
336
|
},
|
|
337
|
+
llm_image_generate: {
|
|
338
|
+
run: async (prompt, opts) => {
|
|
339
|
+
const result = await getImageGeneration(config, { prompt, ...opts });
|
|
340
|
+
return result;
|
|
341
|
+
},
|
|
342
|
+
isAsync: true,
|
|
343
|
+
description: "Generate image",
|
|
344
|
+
arguments: [{ name: "prompt", type: "String" }],
|
|
345
|
+
},
|
|
321
346
|
llm_embedding: {
|
|
322
347
|
run: async (prompt, opts) => {
|
|
323
348
|
const result = await getEmbedding(config, { prompt, ...opts });
|
|
@@ -567,6 +592,139 @@ module.exports = {
|
|
|
567
592
|
else await table.updateRow(upd, row[table.pk_name]);
|
|
568
593
|
},
|
|
569
594
|
},
|
|
595
|
+
llm_generate_image: {
|
|
596
|
+
description: "Generate image with AI based on a text prompt",
|
|
597
|
+
requireRow: true,
|
|
598
|
+
configFields: async ({ table, mode }) => {
|
|
599
|
+
const roleOptions = (await User.get_roles()).map((r) => ({
|
|
600
|
+
value: r.id,
|
|
601
|
+
label: r.role,
|
|
602
|
+
}));
|
|
603
|
+
const commonFields = [
|
|
604
|
+
{
|
|
605
|
+
label: "Minimum role to access",
|
|
606
|
+
name: "min_role",
|
|
607
|
+
input_type: "select",
|
|
608
|
+
sublabel: "User must have this role or higher access image file",
|
|
609
|
+
options: roleOptions,
|
|
610
|
+
},
|
|
611
|
+
];
|
|
612
|
+
if (mode === "workflow") {
|
|
613
|
+
return [
|
|
614
|
+
{
|
|
615
|
+
name: "prompt_template",
|
|
616
|
+
label: "Prompt",
|
|
617
|
+
sublabel:
|
|
618
|
+
"Prompt text. Use interpolations {{ }} to access variables in the context",
|
|
619
|
+
type: "String",
|
|
620
|
+
fieldview: "textarea",
|
|
621
|
+
required: true,
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
name: "answer_field",
|
|
625
|
+
label: "Answer variable",
|
|
626
|
+
sublabel:
|
|
627
|
+
"Set the generated image filename to this context variable",
|
|
628
|
+
type: "String",
|
|
629
|
+
required: true,
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
name: "model",
|
|
633
|
+
label: "Model",
|
|
634
|
+
sublabel: "Override default model name",
|
|
635
|
+
type: "String",
|
|
636
|
+
},
|
|
637
|
+
...commonFields,
|
|
638
|
+
];
|
|
639
|
+
} else if (table) {
|
|
640
|
+
const textFields = table.fields
|
|
641
|
+
.filter((f) => f.type?.sql_name === "text")
|
|
642
|
+
.map((f) => f.name);
|
|
643
|
+
const fileFields = table.fields
|
|
644
|
+
.filter((f) => f.type === "File")
|
|
645
|
+
.map((f) => f.name);
|
|
646
|
+
|
|
647
|
+
return [
|
|
648
|
+
{
|
|
649
|
+
name: "prompt_field",
|
|
650
|
+
label: "Prompt field",
|
|
651
|
+
sublabel: "Field with the text of the prompt",
|
|
652
|
+
type: "String",
|
|
653
|
+
required: true,
|
|
654
|
+
attributes: { options: [...textFields, "Formula"] },
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
name: "prompt_formula",
|
|
658
|
+
label: "Prompt formula",
|
|
659
|
+
type: "String",
|
|
660
|
+
showIf: { prompt_field: "Formula" },
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
name: "answer_field",
|
|
664
|
+
label: "Answer field",
|
|
665
|
+
sublabel: "Output field will be set to the generated image file",
|
|
666
|
+
type: "String",
|
|
667
|
+
required: true,
|
|
668
|
+
attributes: { options: fileFields },
|
|
669
|
+
},
|
|
670
|
+
...commonFields,
|
|
671
|
+
];
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
run: async ({
|
|
675
|
+
row,
|
|
676
|
+
table,
|
|
677
|
+
user,
|
|
678
|
+
mode,
|
|
679
|
+
configuration: {
|
|
680
|
+
prompt_field,
|
|
681
|
+
prompt_formula,
|
|
682
|
+
prompt_template,
|
|
683
|
+
answer_field,
|
|
684
|
+
min_role,
|
|
685
|
+
model,
|
|
686
|
+
},
|
|
687
|
+
}) => {
|
|
688
|
+
let prompt;
|
|
689
|
+
if (mode === "workflow")
|
|
690
|
+
prompt = interpolate(prompt_template, row, user);
|
|
691
|
+
else if (prompt_field === "Formula" || mode === "workflow")
|
|
692
|
+
prompt = eval_expression(
|
|
693
|
+
prompt_formula,
|
|
694
|
+
row,
|
|
695
|
+
user,
|
|
696
|
+
"llm_generate prompt formula"
|
|
697
|
+
);
|
|
698
|
+
else prompt = row[prompt_field];
|
|
699
|
+
const opts = { debugResult: true }; // response_format: "b64_json" };
|
|
700
|
+
|
|
701
|
+
if (model) opts.model = model;
|
|
702
|
+
let history = [];
|
|
703
|
+
|
|
704
|
+
const ans = await getImageGeneration(config, {
|
|
705
|
+
prompt,
|
|
706
|
+
...opts,
|
|
707
|
+
});
|
|
708
|
+
const upd = {};
|
|
709
|
+
|
|
710
|
+
if (ans.url) {
|
|
711
|
+
//fetch url
|
|
712
|
+
} else if (ans.b64_json) {
|
|
713
|
+
const imgContents = Buffer.from(ans.b64_json, "base64");
|
|
714
|
+
const file = await File.from_contents(
|
|
715
|
+
"generated.png",
|
|
716
|
+
"image/png",
|
|
717
|
+
imgContents,
|
|
718
|
+
user?.id,
|
|
719
|
+
min_role || 1
|
|
720
|
+
);
|
|
721
|
+
upd[answer_field] = file.path_to_serve;
|
|
722
|
+
}
|
|
723
|
+
if (mode === "workflow") return upd;
|
|
724
|
+
else await table.updateRow(upd, row[table.pk_name]);
|
|
725
|
+
},
|
|
726
|
+
},
|
|
727
|
+
|
|
570
728
|
llm_generate_json: {
|
|
571
729
|
description:
|
|
572
730
|
"Generate JSON with AI based on a text prompt. You must sppecify the JSON fields in the configuration.",
|