@saltcorn/copilot 0.1.0 → 0.2.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/GenWorkflowCodePage.js +333 -0
- package/database-designer.js +13 -1
- package/index.js +4 -0
- package/package.json +1 -1
- package/workflow-gen.js +299 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
// gen workflow code page from prototype
|
|
2
|
+
|
|
3
|
+
const survey_questions = {
|
|
4
|
+
type: "array",
|
|
5
|
+
description:
|
|
6
|
+
"The questions to ask the user in the form, if step_type is Form",
|
|
7
|
+
items: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
question_title: {
|
|
11
|
+
description: "The question to ask the user",
|
|
12
|
+
type: "string",
|
|
13
|
+
},
|
|
14
|
+
question_type: {
|
|
15
|
+
description: "The type of question",
|
|
16
|
+
type: "string",
|
|
17
|
+
enum: ["Yes/No", "Free text", "Multiple choice", "Integer"],
|
|
18
|
+
},
|
|
19
|
+
variable_name: {
|
|
20
|
+
description:
|
|
21
|
+
"a valid JavaScript identifier as a variable name in which the answer will be stored in the Workflow context",
|
|
22
|
+
type: "string",
|
|
23
|
+
},
|
|
24
|
+
multiple_choice_answer: {
|
|
25
|
+
description:
|
|
26
|
+
"The list of possible answers for multiple choice questions",
|
|
27
|
+
type: "array",
|
|
28
|
+
items: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "A possible answer to a multiple choice question",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const steps = {
|
|
38
|
+
type: "array",
|
|
39
|
+
items: {
|
|
40
|
+
type: "object",
|
|
41
|
+
properties: {
|
|
42
|
+
step_name: {
|
|
43
|
+
description: "The name of this step as a valid Javascript identifier",
|
|
44
|
+
type: "string",
|
|
45
|
+
},
|
|
46
|
+
step_type: {
|
|
47
|
+
description: "The type of workflow step",
|
|
48
|
+
type: "string",
|
|
49
|
+
enum: [
|
|
50
|
+
"Code",
|
|
51
|
+
"Form",
|
|
52
|
+
"Output",
|
|
53
|
+
"Stop",
|
|
54
|
+
"AskAI",
|
|
55
|
+
"Extract",
|
|
56
|
+
"Retrieve",
|
|
57
|
+
"ForLoop",
|
|
58
|
+
"EndForLoop",
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
next_step: {
|
|
62
|
+
description: "The next step in the workflow",
|
|
63
|
+
type: "string",
|
|
64
|
+
},
|
|
65
|
+
code: {
|
|
66
|
+
description:
|
|
67
|
+
"JavaScript code to run, if step_type is Code. The workflow context is in scope, return an object of value to add to the context.",
|
|
68
|
+
type: "string",
|
|
69
|
+
},
|
|
70
|
+
html: {
|
|
71
|
+
description:
|
|
72
|
+
"Html code to output to user, if step_type is Output. Use handlebars to access variables in the workflow context",
|
|
73
|
+
type: "string",
|
|
74
|
+
},
|
|
75
|
+
table_expression: {
|
|
76
|
+
description:
|
|
77
|
+
"Expression for array of objects to output to user, if step_type is Output. Use this to output data from the context in a tabular format",
|
|
78
|
+
type: "string",
|
|
79
|
+
},
|
|
80
|
+
form_questions: survey_questions,
|
|
81
|
+
form_title: {
|
|
82
|
+
description:
|
|
83
|
+
"The title of the form to show to the user, if step_type is Form",
|
|
84
|
+
type: "string",
|
|
85
|
+
},
|
|
86
|
+
ask_question_expression: {
|
|
87
|
+
description:
|
|
88
|
+
"A JavaScript expression for the question to ask (which may be based on variables from the context) if step_type is AskAI. The question should include all relevant context in one long string. Include any relevant information as the person asking the quesiotn is not up-to-date on any specifics",
|
|
89
|
+
type: "string",
|
|
90
|
+
},
|
|
91
|
+
answer_variable: {
|
|
92
|
+
description:
|
|
93
|
+
"The variable in the context to which the answer will be written as a string, if step_type is AskAI",
|
|
94
|
+
type: "string",
|
|
95
|
+
},
|
|
96
|
+
retrieve_term_expression: {
|
|
97
|
+
description:
|
|
98
|
+
"A JavaScript expression for the string containing the term to search for, if step_type is Retrieve. The variables in the context are directly in scope, and the context as a whole can be addressed with the identifier context.",
|
|
99
|
+
type: "string",
|
|
100
|
+
},
|
|
101
|
+
retrieve_to_variable: {
|
|
102
|
+
description:
|
|
103
|
+
"The variable in the context to which the retrieved documents will be appended, if step_type is Retrieve. The variable will be set to be a list of objects each with fields id (an integer), contents (the document contents as a string), title (a shorter string) and url (a link to the document, as a string).",
|
|
104
|
+
type: "string",
|
|
105
|
+
},
|
|
106
|
+
for_loop_array_expression: {
|
|
107
|
+
description:
|
|
108
|
+
"The JavaScript expression for the array to iterate over, if step_type is ForLoop. User this to iterate over an array in the context",
|
|
109
|
+
type: "string",
|
|
110
|
+
},
|
|
111
|
+
for_loop_step_name: {
|
|
112
|
+
description:
|
|
113
|
+
"The name of the step that starts the inner execution of the for loop, if step_type is ForLoop. Each iteration of the loop starts with this step and runs until a step with type EndForLoop is encountered",
|
|
114
|
+
type: "string",
|
|
115
|
+
},
|
|
116
|
+
for_loop_variable: {
|
|
117
|
+
description:
|
|
118
|
+
"The variable name each item in the array will be written as in the context, if step_type is ForLoop. During the execution of the loop, the current item in the array being iterated over can be accessed by this variable name",
|
|
119
|
+
type: "string",
|
|
120
|
+
},
|
|
121
|
+
extract_multiple: {
|
|
122
|
+
description:
|
|
123
|
+
"If true, extract multiple objects with the specified fields pushed as a list. If false, extract a single object with the specified fields. Set if step_type is Extract.",
|
|
124
|
+
type: "boolean",
|
|
125
|
+
},
|
|
126
|
+
extract_to_variable: {
|
|
127
|
+
description:
|
|
128
|
+
"The variable in the context to which the extracted data will be written, if step_type is Extract. The data will be pushed as an object, or an array of objects if extract_multiple is true",
|
|
129
|
+
type: "string",
|
|
130
|
+
},
|
|
131
|
+
extract_from_string_expresssion: {
|
|
132
|
+
description:
|
|
133
|
+
"A JavaScript expression for the string from which information should be extracted, if step_type is Extract. The variables in the context are directly in scope, and the context as a whole can be addressed with the identifier context.",
|
|
134
|
+
type: "string",
|
|
135
|
+
},
|
|
136
|
+
extract_description: {
|
|
137
|
+
description:
|
|
138
|
+
"A general description of what it is that should be extracted, if step_type is Extract.",
|
|
139
|
+
type: "string",
|
|
140
|
+
},
|
|
141
|
+
extract_fields: {
|
|
142
|
+
description:
|
|
143
|
+
"The fields to extract from the text, if step_type is Extract.",
|
|
144
|
+
type: "array",
|
|
145
|
+
items: {
|
|
146
|
+
type: "object",
|
|
147
|
+
properties: {
|
|
148
|
+
name: {
|
|
149
|
+
description: "The field name, as a valid JavaScript identifier",
|
|
150
|
+
type: "string",
|
|
151
|
+
},
|
|
152
|
+
description: {
|
|
153
|
+
description: "A description of the field",
|
|
154
|
+
type: "string",
|
|
155
|
+
},
|
|
156
|
+
type: {
|
|
157
|
+
description: "The field type",
|
|
158
|
+
type: "string",
|
|
159
|
+
enum: ["string", "integer", "number", "boolean"],
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const workflow_function = {
|
|
169
|
+
type: "function",
|
|
170
|
+
function: {
|
|
171
|
+
name: "generate_workflow",
|
|
172
|
+
description: "Generate the steps in a workflow",
|
|
173
|
+
parameters: {
|
|
174
|
+
type: "object",
|
|
175
|
+
properties: {
|
|
176
|
+
workflow_steps: steps,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const workflowSystemPrompt = `You are an expert in constructing computational workflows according to specifications. You must create
|
|
183
|
+
the workflow by calling the generate_workflow tool, with the step required to implement the specification.
|
|
184
|
+
|
|
185
|
+
The steps are specified as JSON objects. Each step has a name, specified in the step_name key in the JSON object.
|
|
186
|
+
The step name should be a valid JavaScript identifier.
|
|
187
|
+
|
|
188
|
+
Each run of the workflow is executed in the presence of a context, which is a JavaScript object that individual
|
|
189
|
+
steps can read values from and write values to. This context is a state that is persisted on disk for each workflow
|
|
190
|
+
run.
|
|
191
|
+
|
|
192
|
+
Each step can have a next_step key which is the name of the next step, or a JavaScript expression which evaluates
|
|
193
|
+
to the name of the next step based on the context. In the evaluation of the next step, each value in the context is
|
|
194
|
+
in scope and can be addressed directly. Identifiers for the step names are also in scope, the name of the next step
|
|
195
|
+
can be used directly without enclosing it in quotes to form a string.
|
|
196
|
+
|
|
197
|
+
For example, if the context contains a value x which is an integer and you have steps named "too_low" and "too_high",
|
|
198
|
+
and you would like the next step to be too_low if x is less than 10 and too_high otherwise,
|
|
199
|
+
use this as the next_step expression: x<10 ? too_low : too_high
|
|
200
|
+
|
|
201
|
+
If the next_step is omitted then the next step is the following step in the array of steps.
|
|
202
|
+
|
|
203
|
+
The workflow steps can have different types according to what each step should accomplish. The available step types,
|
|
204
|
+
set in the "step_type" key in the step object, are Code, Form, Output, AskAI, Extract, Retrieve, ForLoop, EndForLoop and Stop. Here are some details of each step type:
|
|
205
|
+
|
|
206
|
+
Code: if the step_type is "Code" then the step object should include the JavaScript code to be executed in the "code"
|
|
207
|
+
key. You can use await in the code if you need to run asynchronous code. The values in the context are directly in scope and can be accessed using their name. In addition, the variable
|
|
208
|
+
"context" it's also in scope and can be used to address the context as a whole. To write values to the context, return an
|
|
209
|
+
object. The values in this object will be written into the current context. If a value already exists in the context
|
|
210
|
+
it will be overridden. For example, If the context contains values x and x which are numbere and you would like to push
|
|
211
|
+
the value "sum" which is the sum of x and y, then use this as the code: return {sum: x+y}. You cannot set the next step in the
|
|
212
|
+
return object or by returning a string from a Code step, this will not work. To set the next step from a code action, always use the next_step property of the step object.
|
|
213
|
+
This expression for the next step can depend on value pushed to the context (by the return object in the code) as these values are in scope.
|
|
214
|
+
|
|
215
|
+
Form: Use a step with step_type of "Form" to ask questions or obtain information from the user. The execution of the
|
|
216
|
+
workflow is suspended until the user answers the questions by filling in the form.
|
|
217
|
+
|
|
218
|
+
Output: steps with step_type of "Output" can output data from the context to the user. It can do so by writing html, with
|
|
219
|
+
handlebars interpolations to access the context, or by displaying a table from arrays of objects. To output HTML,
|
|
220
|
+
put the HTML you want to be displayed in the html key of
|
|
221
|
+
the step object. This HTML can use handlebars (starting with {{ and ending with }}) to access variables in the context.
|
|
222
|
+
For example if the context includes a value x, you can output this by {{ x }}, for example with this HTML:
|
|
223
|
+
<div>x={{x}}</div>. To output a table, out a JavaScript expression for the data to be displayed in the stop key
|
|
224
|
+
table_expression. For the table_expression exprssion, the variables in the
|
|
225
|
+
context are in scope, as well as the context as a whole by the identifier context.
|
|
226
|
+
|
|
227
|
+
AskAI: use a AskAI step to consult an artificial intelligence language processor to ask a question in natural language in which the answer is given in natural language. The answer is based on a
|
|
228
|
+
question, specified as a JavaScript expression in the step object "ask_question_expression" key. Running the step will provide an answer by a
|
|
229
|
+
highly capable artificial intelligence processor who however does not have in-depth knowledge of the subject matter or any case specifics at hand - you
|
|
230
|
+
must provide all these details in the question string, which should concatenate multiple background documents before asking the
|
|
231
|
+
actual question. You must also provide a variable name (in the answer_variable key in the step definition) where the answer
|
|
232
|
+
will be pushed to the context as a string.
|
|
233
|
+
|
|
234
|
+
Extract: use Extract steps to extract structured information from text. Extract uses natural language processing to read a document,
|
|
235
|
+
and to generate JSON objects with specified fields. An Extract step requires four settings in the step object: extract_description,
|
|
236
|
+
a general description of what it is that should be extracted; extract_fields, which is an array of the fields in each object that is
|
|
237
|
+
extracted from the text, each with a name, a type and a description; extract_multiple a bully and that indicates whether exactly one object
|
|
238
|
+
or an array with any number of objects should be extracted; and extract_to_variable, the name of the variable should be written to in the
|
|
239
|
+
context (as an object if extract_multiple is false and as an array if extract_multiple is true).
|
|
240
|
+
|
|
241
|
+
Retrieve: Retrieve steps will search a document database for a search term, and will push any matching documents to a variable in the
|
|
242
|
+
context. An Retrieve step requires two settings in the step object. Firstly, retrieve_term_expression is a JavaScript expression for the term to
|
|
243
|
+
search for. This expression can be based on the context, each of which values are directly in scope as well as the term context for the context as a whole).
|
|
244
|
+
Secondly, retrieve_to_variable is the name of the variable containing an array any retrieved documents will be appended to. Each document will be
|
|
245
|
+
appended to the array denoted by retrieve_to_variable as an object containing these fields: id, an integer, which is a unique identifier for the document;
|
|
246
|
+
contents, a string, which is the main content of the document; title, a string containing the title; and url, a string containing a URL link to the original document.
|
|
247
|
+
|
|
248
|
+
ForLoop: ForLoop steps loop over an array which is specified by the for_loop_array_expression JavaScript expression. Execution of the workflow steps is temporarily diverted to another set
|
|
249
|
+
of steps, starting from the step specified by the for_loop_step_name value, and runs until it encounters an
|
|
250
|
+
EndForLoop step at which point the next iteration (over the next item in the array) is started. When all items have
|
|
251
|
+
been iterated over, the for loop is complete and execution continues with the next_step of the ForLoop step. During each iteration
|
|
252
|
+
of the loop, the current array item is temporarily set to a variable in the context specified by the for_loop_variable varaible. The steps between
|
|
253
|
+
the for_loop_step_name and the EndForLoop can access this current aray items in the context by the context for_loop_variable name.
|
|
254
|
+
When all items have been iterated, the for loop will continue from the step indicated by its next_step property of the EndForLoop step.
|
|
255
|
+
|
|
256
|
+
EndForLoop: Finish the current iteration of the for loop. The execution goes to the next iteration of the loop, or if
|
|
257
|
+
there are no more items in the list, goes to the next_step of the this, the EndForLoop, step. The next_step of the ForLoop step is ignored.
|
|
258
|
+
|
|
259
|
+
Stop: Stop executing the workflow, the workflow is completed.
|
|
260
|
+
`;
|
|
261
|
+
|
|
262
|
+
async function genWorkflow(row) {
|
|
263
|
+
const toolargs = {
|
|
264
|
+
tools: [workflow_function],
|
|
265
|
+
tool_choice: { type: "function", function: { name: "generate_workflow" } },
|
|
266
|
+
systemPrompt: workflowSystemPrompt,
|
|
267
|
+
};
|
|
268
|
+
const prompt = `Design a workflow to implement a workflow accorfing to the following specification: ${row.description}`;
|
|
269
|
+
console.log(prompt);
|
|
270
|
+
const answer = await llm_generate(prompt, toolargs);
|
|
271
|
+
const resp = JSON.parse(answer.tool_calls[0].function.arguments);
|
|
272
|
+
console.log(JSON.stringify(resp, null, 2));
|
|
273
|
+
const stepTable = Table.findOne("Workflow Steps");
|
|
274
|
+
let ix = 0;
|
|
275
|
+
const db_steps = [];
|
|
276
|
+
for (const step of resp.workflow_steps) {
|
|
277
|
+
ix += 1;
|
|
278
|
+
const { step_name, step_type, next_step, ...config } = step;
|
|
279
|
+
const db_step = {
|
|
280
|
+
name: step_name,
|
|
281
|
+
type: step_type,
|
|
282
|
+
number: ix,
|
|
283
|
+
workflow: row.id,
|
|
284
|
+
next_step: next_step,
|
|
285
|
+
configuration: config,
|
|
286
|
+
};
|
|
287
|
+
db_step.id = await stepTable.insertRow(db_step);
|
|
288
|
+
|
|
289
|
+
db_steps.push(db_step);
|
|
290
|
+
}
|
|
291
|
+
const diagram = genWorkflowDiagram(db_steps);
|
|
292
|
+
await Table.findOne("Workflows").updateRow({ diagram }, row.id);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function genWorkflowDiagram(steps) {
|
|
296
|
+
const stepNames = steps.map((s) => s.name);
|
|
297
|
+
const nodeLines = steps
|
|
298
|
+
.map(
|
|
299
|
+
(s) => ` ${s.name}["\`**${s.name}**
|
|
300
|
+
${s.type}\`"]:::wfstep${s.id}`
|
|
301
|
+
)
|
|
302
|
+
.join("\n");
|
|
303
|
+
const linkLines = [];
|
|
304
|
+
let step_ix = 0;
|
|
305
|
+
for (const step of steps) {
|
|
306
|
+
if (step.type === "ForLoop") {
|
|
307
|
+
linkLines.push(
|
|
308
|
+
` ${step.name} --> ${step.configuration.for_loop_step_name}`
|
|
309
|
+
);
|
|
310
|
+
} else if (stepNames.includes(step.next_step)) {
|
|
311
|
+
linkLines.push(` ${step.name} --> ${step.next_step}`);
|
|
312
|
+
} else if (!step.next_step && steps[step_ix + 1])
|
|
313
|
+
linkLines.push(` ${step.name} --> ${steps[step_ix + 1].name}`);
|
|
314
|
+
else if (step.next_step) {
|
|
315
|
+
for (otherStep of stepNames)
|
|
316
|
+
if (step.next_step.includes(otherStep))
|
|
317
|
+
linkLines.push(` ${step.name} --> ${otherStep}`);
|
|
318
|
+
}
|
|
319
|
+
if (step.type === "EndForLoop") {
|
|
320
|
+
// TODO this is not correct. improve.
|
|
321
|
+
let forStep;
|
|
322
|
+
for (let i = step_ix; i >= 0; i -= 1) {
|
|
323
|
+
if (steps[i].type === "ForLoop") {
|
|
324
|
+
forStep = steps[i];
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (forStep) linkLines.push(` ${step.name} --> ${forStep.name}`);
|
|
329
|
+
}
|
|
330
|
+
step_ix += 1;
|
|
331
|
+
}
|
|
332
|
+
return "flowchart TD\n" + nodeLines + "\n" + linkLines.join("\n");
|
|
333
|
+
}
|
package/database-designer.js
CHANGED
|
@@ -114,6 +114,7 @@ const runPost = async (
|
|
|
114
114
|
|
|
115
115
|
const moreTypes = {
|
|
116
116
|
decimal: "Float",
|
|
117
|
+
numeric: "Float",
|
|
117
118
|
varchar: "String",
|
|
118
119
|
};
|
|
119
120
|
|
|
@@ -137,14 +138,24 @@ const save_database = async (table_id, viewname, config, body, { req }) => {
|
|
|
137
138
|
definition,
|
|
138
139
|
primary_key,
|
|
139
140
|
reference_definition,
|
|
141
|
+
resource,
|
|
140
142
|
} of create_definitions) {
|
|
141
143
|
if (primary_key) continue;
|
|
144
|
+
if (resource === "constraint") continue;
|
|
145
|
+
|
|
142
146
|
let type =
|
|
143
147
|
findType(definition.dataType.toLowerCase()) ||
|
|
144
148
|
moreTypes[definition.dataType.toLowerCase()];
|
|
145
149
|
if (reference_definition)
|
|
146
150
|
type = `Key to ${reference_definition.table[0].table}`;
|
|
147
|
-
|
|
151
|
+
const constraint = create_definitions.find(
|
|
152
|
+
(cd) =>
|
|
153
|
+
cd.resource === "constraint" &&
|
|
154
|
+
cd.definition?.[0]?.column === column.column
|
|
155
|
+
);
|
|
156
|
+
if (constraint?.reference_definition) {
|
|
157
|
+
type = `Key to ${constraint.reference_definition.table[0].table}`;
|
|
158
|
+
}
|
|
148
159
|
fields.push({
|
|
149
160
|
name: column.column,
|
|
150
161
|
type,
|
|
@@ -161,6 +172,7 @@ const save_database = async (table_id, viewname, config, body, { req }) => {
|
|
|
161
172
|
|
|
162
173
|
for (const field of tbl.fields) {
|
|
163
174
|
field.table = table;
|
|
175
|
+
console.log("field", field.name, "type", field.type);
|
|
164
176
|
//pick summary field
|
|
165
177
|
if (field.type === "Key to users") {
|
|
166
178
|
field.attributes = { summary_field: "email" };
|
package/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const Workflow = require("@saltcorn/data/models/workflow");
|
|
2
2
|
const Form = require("@saltcorn/data/models/form");
|
|
3
|
+
const { features } = require("@saltcorn/data/db/state");
|
|
3
4
|
|
|
4
5
|
const configuration_workflow = () =>
|
|
5
6
|
new Workflow({
|
|
@@ -11,4 +12,7 @@ module.exports = {
|
|
|
11
12
|
//configuration_workflow,
|
|
12
13
|
dependencies: ["@saltcorn/large-language-model"],
|
|
13
14
|
viewtemplates: [require("./action-builder"), require("./database-designer")],
|
|
15
|
+
functions: features.workflows
|
|
16
|
+
? { copilot_generate_workflow: require("./workflow-gen") }
|
|
17
|
+
: {},
|
|
14
18
|
};
|
package/package.json
CHANGED
package/workflow-gen.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
2
|
+
const WorkflowStep = require("@saltcorn/data/models/workflow_step");
|
|
3
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
4
|
+
const { getActionConfigFields } = require("@saltcorn/data/plugin-helper");
|
|
5
|
+
|
|
6
|
+
const workflowSystemPrompt = () => {
|
|
7
|
+
const actionExplainers = WorkflowStep.builtInActionExplainers();
|
|
8
|
+
let stateActions = getState().actions;
|
|
9
|
+
const stateActionList = Object.entries(stateActions).filter(
|
|
10
|
+
([k, v]) => !v.disableInWorkflow
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
return `You are an expert in constructing computational workflows according to specifications. You must create
|
|
14
|
+
the workflow by calling the generate_workflow tool, with the step required to implement the specification.
|
|
15
|
+
|
|
16
|
+
The steps are specified as JSON objects. Each step has a name, specified in the step_name key in the JSON object.
|
|
17
|
+
The step name should be a valid JavaScript identifier.
|
|
18
|
+
|
|
19
|
+
Each run of the workflow is executed in the presence of a context, which is a JavaScript object that individual
|
|
20
|
+
steps can read values from and write values to. This context is a state that is persisted on disk for each workflow
|
|
21
|
+
run.
|
|
22
|
+
|
|
23
|
+
Each step can have a next_step key which is the name of the next step, or a JavaScript expression which evaluates
|
|
24
|
+
to the name of the next step based on the context. In the evaluation of the next step, each value in the context is
|
|
25
|
+
in scope and can be addressed directly. Identifiers for the step names are also in scope, the name of the next step
|
|
26
|
+
can be used directly without enclosing it in quotes to form a string.
|
|
27
|
+
|
|
28
|
+
For example, if the context contains a value x which is an integer and you have steps named "too_low" and "too_high",
|
|
29
|
+
and you would like the next step to be too_low if x is less than 10 and too_high otherwise,
|
|
30
|
+
use this as the next_step expression: x<10 ? too_low : too_high
|
|
31
|
+
|
|
32
|
+
If the next_step is omitted then the workflow terminates.
|
|
33
|
+
|
|
34
|
+
Each step has a step_configuration object which contains the step type and the specific parameters of
|
|
35
|
+
that step type. You should specify the step type in the step_type subfield of the step_configuration
|
|
36
|
+
field. The available step types are:
|
|
37
|
+
|
|
38
|
+
${Object.entries(actionExplainers)
|
|
39
|
+
.map(([k, v]) => `* ${k}: ${v}`)
|
|
40
|
+
.join("\n")}
|
|
41
|
+
${stateActionList.map(([k, v]) => `* ${k}: ${v.description || ""}`).join("\n")}
|
|
42
|
+
|
|
43
|
+
Most of them are are explained by their parameter descriptions. Here are some additional information for some
|
|
44
|
+
step types:
|
|
45
|
+
|
|
46
|
+
run_js_code: if the step_type is "run_js_code" then the step object should include the JavaScript code to be executed in the "code"
|
|
47
|
+
key. You can use await in the code if you need to run asynchronous code. The values in the context are directly in scope and can be accessed using their name. In addition, the variable
|
|
48
|
+
"context" is also in scope and can be used to address the context as a whole. To write values to the context, return an
|
|
49
|
+
object. The values in this object will be written into the current context. If a value already exists in the context
|
|
50
|
+
it will be overwritten. For example, If the context contains values x and y which are numbers and you would like to push
|
|
51
|
+
the value "sum" which is the sum of x and y, then use this as the code: return {sum: x+y}. You cannot set the next step in the
|
|
52
|
+
return object or by returning a string from a run_js_code step, this will not work. To set the next step from a code action, always use the next_step property of the step object.
|
|
53
|
+
This expression for the next step can depend on value pushed to the context (by the return object in the code) as these values are in scope.
|
|
54
|
+
|
|
55
|
+
ForLoop: ForLoop steps loop over an array which is specified by the array_expression JavaScript expression. Execution of the workflow steps is temporarily diverted to another set
|
|
56
|
+
of steps, starting from the step specified by the loop_body_inital_step value, and runs until it encounters a
|
|
57
|
+
step with nothing specified for next_step at which point the next iteration (over the next item in the array) is started. When all items have
|
|
58
|
+
been iterated over, the for loop is complete and execution continues with the next_step of the ForLoop step. During each iteration
|
|
59
|
+
of the loop, the current array item is temporarily set to a variable in the context specified by the item_variable variable. The steps between
|
|
60
|
+
in the loop body can access this current aray items in the context by the context item_variable name.
|
|
61
|
+
When all items have been iterated, the for loop will continue from the step indicated by its next_step.
|
|
62
|
+
|
|
63
|
+
llm_generate: use a llm_generate step to consult an artificial intelligence language processor to ask a question in natural language in which the answer is given in natural language. The answer is based on a
|
|
64
|
+
question, specified as a string in the step conmfiguration "prompt_template" key in which you can user interpolation ({{ }}) to access context variables. Running the step will provide an answer by a
|
|
65
|
+
highly capable artificial intelligence processor who however does not have in-depth knowledge of the subject matter or any case specifics at hand - you
|
|
66
|
+
must provide all these details in the question string, which should concatenate multiple background documents before asking the
|
|
67
|
+
actual question. You must also provide a variable name (in the answer_field key in the step definition) where the answer
|
|
68
|
+
will be pushed to the context as a string. If you specificy a variable name in chat_history_field, the invocation of subsequent llm_generate
|
|
69
|
+
steps in the same workflow will contain the interaction history of previous invocations, so you don't have to repeat information in the prompt and can
|
|
70
|
+
maintain a conversational interaction.
|
|
71
|
+
|
|
72
|
+
llm_generate_json: use llm_generate_json steps to extract structured information from text. llm_generate_json uses natural language processing to read a document,
|
|
73
|
+
and to generate JSON objects with specified fields. A llm_generate_json step requires four settings in the step object: gen_description,
|
|
74
|
+
a general description of what it is that should be extracted; fields, which is an array of the fields in each object that is
|
|
75
|
+
extracted from the text, each with a name, a type and a description; multiple a boolean and that indicates whether exactly one object
|
|
76
|
+
or an array with any number of objects should be extracted; and answer_field, the name of the variable should be written to in the
|
|
77
|
+
context (as an object if multiple is false and as an array if multiple is true).
|
|
78
|
+
|
|
79
|
+
`;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const survey_questions = {
|
|
83
|
+
type: "array",
|
|
84
|
+
description:
|
|
85
|
+
"The questions to ask the user in the form, if step_type is Form",
|
|
86
|
+
items: {
|
|
87
|
+
type: "object",
|
|
88
|
+
properties: {
|
|
89
|
+
question_title: {
|
|
90
|
+
description: "The question to ask the user",
|
|
91
|
+
type: "string",
|
|
92
|
+
},
|
|
93
|
+
question_type: {
|
|
94
|
+
description: "The type of question",
|
|
95
|
+
type: "string",
|
|
96
|
+
enum: ["Yes/No", "Free text", "Multiple choice", "Integer"],
|
|
97
|
+
},
|
|
98
|
+
variable_name: {
|
|
99
|
+
description:
|
|
100
|
+
"a valid JavaScript identifier as a variable name in which the answer will be stored in the Workflow context",
|
|
101
|
+
type: "string",
|
|
102
|
+
},
|
|
103
|
+
multiple_choice_answer: {
|
|
104
|
+
description:
|
|
105
|
+
"The list of possible answers for multiple choice questions",
|
|
106
|
+
type: "array",
|
|
107
|
+
items: {
|
|
108
|
+
type: "string",
|
|
109
|
+
description: "A possible answer to a multiple choice question",
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
const toArrayOfStrings = (opts) => {
|
|
116
|
+
if (typeof opts === "string") return opts.split(",").map((s) => s.trim());
|
|
117
|
+
if (Array.isArray(opts))
|
|
118
|
+
return opts.map((o) => (typeof o === "string" ? o : o.value || o.name));
|
|
119
|
+
};
|
|
120
|
+
const fieldProperties = (field) => {
|
|
121
|
+
const props = {};
|
|
122
|
+
const typeName = field.type?.name || field.type || field.input_type;
|
|
123
|
+
if (field.isRepeat) {
|
|
124
|
+
props.type = "array";
|
|
125
|
+
const properties = {};
|
|
126
|
+
field.fields.map((f) => {
|
|
127
|
+
properties[f.name] = {
|
|
128
|
+
description: f.sublabel || f.label,
|
|
129
|
+
...fieldProperties(f),
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
props.items = {
|
|
133
|
+
type: "object",
|
|
134
|
+
properties,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
switch (typeName) {
|
|
138
|
+
case "String":
|
|
139
|
+
props.type = "string";
|
|
140
|
+
if (field.attributes?.options)
|
|
141
|
+
props.enum = toArrayOfStrings(field.attributes.options);
|
|
142
|
+
break;
|
|
143
|
+
case "Boolean":
|
|
144
|
+
props.type = "boolean";
|
|
145
|
+
break;
|
|
146
|
+
case "Integer":
|
|
147
|
+
props.type = "integer";
|
|
148
|
+
break;
|
|
149
|
+
case "Float":
|
|
150
|
+
props.type = "number";
|
|
151
|
+
break;
|
|
152
|
+
case "select":
|
|
153
|
+
props.type = "string";
|
|
154
|
+
if (field.options) props.enum = toArrayOfStrings(field.options);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
return props;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const steps = async () => {
|
|
161
|
+
const actionExplainers = WorkflowStep.builtInActionExplainers();
|
|
162
|
+
const actionFields = await WorkflowStep.builtInActionConfigFields();
|
|
163
|
+
|
|
164
|
+
let stateActions = getState().actions;
|
|
165
|
+
const stateActionList = Object.entries(stateActions).filter(
|
|
166
|
+
([k, v]) => !v.disableInWorkflow
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const stepTypeAndCfg = Object.keys(actionExplainers).map((actionName) => {
|
|
170
|
+
const properties = { step_type: { const: actionName } };
|
|
171
|
+
const myFields = actionFields.filter(
|
|
172
|
+
(f) => f.showIf?.wf_action_name === actionName
|
|
173
|
+
);
|
|
174
|
+
const required = ["step_type"];
|
|
175
|
+
myFields.forEach((f) => {
|
|
176
|
+
if (f.required) required.push(f.name);
|
|
177
|
+
properties[f.name] = {
|
|
178
|
+
description: f.sublabel || f.label,
|
|
179
|
+
...fieldProperties(f),
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
return {
|
|
183
|
+
type: "object",
|
|
184
|
+
description: actionExplainers[actionName],
|
|
185
|
+
properties,
|
|
186
|
+
required,
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
for (const [actionName, action] of stateActionList) {
|
|
190
|
+
try {
|
|
191
|
+
const properties = { step_type: { const: actionName } };
|
|
192
|
+
const cfgFields = await getActionConfigFields(action, null, {
|
|
193
|
+
mode: "workflow",
|
|
194
|
+
});
|
|
195
|
+
const required = ["step_type"];
|
|
196
|
+
cfgFields.forEach((f) => {
|
|
197
|
+
if (f.input_type === "section_header") return;
|
|
198
|
+
if (f.required) required.push(f.name);
|
|
199
|
+
properties[f.name] = {
|
|
200
|
+
description: f.sublabel || f.label,
|
|
201
|
+
...fieldProperties(f),
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
stepTypeAndCfg.push({
|
|
205
|
+
type: "object",
|
|
206
|
+
description: actionExplainers[actionName],
|
|
207
|
+
properties,
|
|
208
|
+
required,
|
|
209
|
+
});
|
|
210
|
+
} catch (e) {}
|
|
211
|
+
}
|
|
212
|
+
const properties = {
|
|
213
|
+
step_name: {
|
|
214
|
+
description: "The name of this step as a valid Javascript identifier",
|
|
215
|
+
type: "string",
|
|
216
|
+
},
|
|
217
|
+
only_if: {
|
|
218
|
+
description:
|
|
219
|
+
"Optional JavaScript expression based on the context. If given, the chosen action will only be executed if evaluates to true",
|
|
220
|
+
type: "string",
|
|
221
|
+
},
|
|
222
|
+
/*step_type: {
|
|
223
|
+
description: "The type of workflow step",
|
|
224
|
+
type: "string",
|
|
225
|
+
enum: Object.keys(actionExplainers),
|
|
226
|
+
},*/
|
|
227
|
+
next_step: {
|
|
228
|
+
description:
|
|
229
|
+
"The next step in the workflow, as a JavaScript expression based on the context.",
|
|
230
|
+
type: "string",
|
|
231
|
+
},
|
|
232
|
+
step_configuration: { anyOf: stepTypeAndCfg },
|
|
233
|
+
};
|
|
234
|
+
return {
|
|
235
|
+
type: "array",
|
|
236
|
+
items: {
|
|
237
|
+
type: "object",
|
|
238
|
+
properties,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
const workflow_function = async () => ({
|
|
243
|
+
type: "function",
|
|
244
|
+
function: {
|
|
245
|
+
name: "generate_workflow",
|
|
246
|
+
description: "Generate the steps in a workflow",
|
|
247
|
+
parameters: {
|
|
248
|
+
type: "object",
|
|
249
|
+
properties: {
|
|
250
|
+
workflow_steps: await steps(),
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
module.exports = {
|
|
257
|
+
run: async (description) => {
|
|
258
|
+
const rnd = Math.round(100 * Math.random());
|
|
259
|
+
const systemPrompt = workflowSystemPrompt();
|
|
260
|
+
console.log(systemPrompt);
|
|
261
|
+
|
|
262
|
+
const toolargs = {
|
|
263
|
+
tools: [await workflow_function()],
|
|
264
|
+
tool_choice: {
|
|
265
|
+
type: "function",
|
|
266
|
+
function: { name: "generate_workflow" },
|
|
267
|
+
},
|
|
268
|
+
systemPrompt,
|
|
269
|
+
};
|
|
270
|
+
const prompt = `Design a workflow to implement a workflow accorfing to the following specification: ${description}`;
|
|
271
|
+
console.log(prompt);
|
|
272
|
+
console.log(JSON.stringify(toolargs, null, 2));
|
|
273
|
+
|
|
274
|
+
const answer = await getState().functions.llm_generate.run(
|
|
275
|
+
prompt,
|
|
276
|
+
toolargs
|
|
277
|
+
);
|
|
278
|
+
const resp = JSON.parse(answer.tool_calls[0].function.arguments);
|
|
279
|
+
console.log(JSON.stringify(resp, null, 2));
|
|
280
|
+
const scsteps = resp.workflow_steps.map(to_saltcorn_step);
|
|
281
|
+
console.log("scteps", scsteps);
|
|
282
|
+
|
|
283
|
+
return scsteps;
|
|
284
|
+
},
|
|
285
|
+
isAsync: true,
|
|
286
|
+
description: "Generate a workflow",
|
|
287
|
+
arguments: [{ name: "description", type: "String" }],
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const to_saltcorn_step = (llm_step) => {
|
|
291
|
+
const { step_type, ...configuration } = llm_step.step_configuration;
|
|
292
|
+
return {
|
|
293
|
+
name: llm_step.step_name,
|
|
294
|
+
action_name: step_type,
|
|
295
|
+
next_step: llm_step.next_step,
|
|
296
|
+
only_if: llm_step.only_if,
|
|
297
|
+
configuration,
|
|
298
|
+
};
|
|
299
|
+
};
|