@saltcorn/agents 0.2.3 → 0.3.1

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/agent-view.js CHANGED
@@ -39,6 +39,7 @@ const {
39
39
  wrapCard,
40
40
  wrapSegment,
41
41
  process_interaction,
42
+ find_image_tool,
42
43
  } = require("./common");
43
44
  const MarkdownIt = require("markdown-it"),
44
45
  md = new MarkdownIt();
@@ -157,7 +158,6 @@ const run = async (
157
158
  for (const interact of run.context.interactions) {
158
159
  switch (interact.role) {
159
160
  case "user":
160
- console.log(interact.content);
161
161
  if (interact.content?.[0]?.type === "image_url") {
162
162
  const image_url = interact.content[0].image_url.url;
163
163
  if (image_url.startsWith("data"))
@@ -187,58 +187,74 @@ const run = async (
187
187
  break;
188
188
  case "assistant":
189
189
  case "system":
190
- if (interact.tool_calls) {
191
- if (interact.content) {
192
- interactMarkups.push(
193
- div(
194
- { class: "interaction-segment" },
195
- span({ class: "badge bg-secondary" }, action.name),
196
- typeof interact.content === "string"
197
- ? md.render(interact.content)
198
- : interact.content
199
- )
200
- );
190
+
191
+ for (const tool_call of interact.tool_calls || []) {
192
+ const toolSkill = find_tool(
193
+ tool_call.function.name,
194
+ action.configuration
195
+ );
196
+ if (toolSkill) {
197
+ const row = JSON.parse(tool_call.function.arguments);
198
+ if (toolSkill.tool.renderToolCall) {
199
+ const rendered = await toolSkill.tool.renderToolCall(row, {
200
+ req,
201
+ });
202
+ if (rendered)
203
+ interactMarkups.push(
204
+ wrapSegment(
205
+ wrapCard(
206
+ toolSkill.skill.skill_label ||
207
+ toolSkill.skill.constructor.skill_name,
208
+ rendered
209
+ ),
210
+ action.name
211
+ )
212
+ );
213
+ }
201
214
  }
202
- for (const tool_call of interact.tool_calls) {
203
- const toolSkill = find_tool(
204
- tool_call.function.name,
205
- action.configuration
206
- );
207
- if (toolSkill) {
208
- const row = JSON.parse(tool_call.function.arguments);
209
- if (toolSkill.tool.renderToolCall) {
210
- const rendered = await toolSkill.tool.renderToolCall(row, {
215
+ }
216
+ for (const image_call of interact.content?.image_calls || []) {
217
+
218
+ const toolSkill = find_image_tool(action.configuration);
219
+ if (toolSkill) {
220
+ if (toolSkill.tool.renderToolResponse) {
221
+ const rendered = await toolSkill.tool.renderToolResponse(
222
+ image_call,
223
+ {
211
224
  req,
212
- });
213
- if (rendered)
214
- interactMarkups.push(
215
- wrapSegment(
216
- wrapCard(
217
- toolSkill.skill.skill_label ||
218
- toolSkill.skill.constructor.skill_name,
219
- rendered
220
- ),
221
- action.name
222
- )
223
- );
224
- }
225
+ }
226
+ );
227
+
228
+ if (rendered)
229
+ interactMarkups.push(
230
+ wrapSegment(
231
+ wrapCard(
232
+ toolSkill.skill.skill_label ||
233
+ toolSkill.skill.constructor.skill_name,
234
+ rendered
235
+ ),
236
+ action.name
237
+ )
238
+ );
225
239
  }
226
240
  }
227
- } else
228
- interactMarkups.push(
229
- div(
230
- { class: "interaction-segment" },
231
- span({ class: "badge bg-secondary" }, action.name),
232
- typeof interact.content === "string"
233
- ? md.render(interact.content)
234
- : interact.content
235
- )
236
- );
241
+ }
242
+
243
+ interactMarkups.push(
244
+ div(
245
+ { class: "interaction-segment" },
246
+ span({ class: "badge bg-secondary" }, action.name),
247
+ typeof interact.content === "string"
248
+ ? md.render(interact.content)
249
+ : typeof interact.content?.content === "string"
250
+ ? md.render(interact.content.content)
251
+ : interact.content
252
+ )
253
+ );
237
254
  break;
238
255
  case "tool":
239
256
  if (interact.content !== "Action run") {
240
257
  let markupContent;
241
- console.log("interact", interact);
242
258
  const toolSkill = find_tool(interact.name, action.configuration);
243
259
  try {
244
260
  if (toolSkill?.tool?.renderToolResponse)
package/common.js CHANGED
@@ -11,6 +11,8 @@ const get_skills = () => {
11
11
  require("./skills/EmbeddingRetrieval"),
12
12
  require("./skills/Trigger"),
13
13
  require("./skills/Table"),
14
+ require("./skills/PreloadData"),
15
+ require("./skills/GenerateImage"),
14
16
  //require("./skills/AdaptiveFeedback"),
15
17
  ];
16
18
  };
@@ -33,7 +35,7 @@ const get_skill_instances = (config) => {
33
35
  const find_tool = (name, config) => {
34
36
  const skills = get_skill_instances(config);
35
37
  for (const skill of skills) {
36
- const skillTools = skill.provideTools();
38
+ const skillTools = skill.provideTools?.();
37
39
  const tools = !skillTools
38
40
  ? []
39
41
  : Array.isArray(skillTools)
@@ -44,16 +46,30 @@ const find_tool = (name, config) => {
44
46
  }
45
47
  };
46
48
 
47
- const getCompletionArguments = async (config) => {
49
+ const find_image_tool = (config) => {
50
+ const skills = get_skill_instances(config);
51
+ for (const skill of skills) {
52
+ const skillTools = skill.provideTools?.();
53
+ const tools = !skillTools
54
+ ? []
55
+ : Array.isArray(skillTools)
56
+ ? skillTools
57
+ : [skillTools];
58
+ const found = tools.find((t) => t?.type === "image_generation");
59
+ if (found) return { tool: found, skill };
60
+ }
61
+ };
62
+
63
+ const getCompletionArguments = async (config, user) => {
48
64
  let tools = [];
49
65
 
50
66
  let sysPrompts = [config.sys_prompt];
51
67
 
52
68
  const skills = get_skill_instances(config);
53
69
  for (const skill of skills) {
54
- const sysPr = skill.systemPrompt();
70
+ const sysPr = await skill.systemPrompt?.({ user });
55
71
  if (sysPr) sysPrompts.push(sysPr);
56
- const skillTools = skill.provideTools();
72
+ const skillTools = skill.provideTools?.();
57
73
  if (skillTools && Array.isArray(skillTools)) tools.push(...skillTools);
58
74
  else if (skillTools) tools.push(skillTools);
59
75
  }
@@ -129,13 +145,46 @@ const process_interaction = async (
129
145
  agent_label = "Copilot",
130
146
  prevResponses = []
131
147
  ) => {
132
- const complArgs = await getCompletionArguments(config);
148
+ const complArgs = await getCompletionArguments(config, req.user);
133
149
  complArgs.chat = run.context.interactions;
134
150
  //complArgs.debugResult = true;
135
- console.log("complArgs", JSON.stringify(complArgs, null, 2));
151
+ //console.log("complArgs", JSON.stringify(complArgs, null, 2));
136
152
 
137
153
  const answer = await getState().functions.llm_generate.run("", complArgs);
138
- console.log("answer", answer);
154
+ //console.log("answer", answer);
155
+
156
+ const responses = [];
157
+ if (typeof answer === "object" && answer.image_calls) {
158
+ for (const image_call of answer.image_calls) {
159
+ const tool = find_image_tool(config);
160
+ let prcRes;
161
+ if (tool?.tool.process) {
162
+ prcRes = await tool.tool.process(image_call, { req });
163
+ if (prcRes?.result === null) delete image_call.result;
164
+ if (prcRes?.filename) image_call.filename = prcRes?.filename;
165
+ }
166
+ if (tool?.tool.renderToolResponse) {
167
+ const rendered = await tool.tool.renderToolResponse(
168
+ { ...image_call, ...(prcRes || {}) },
169
+ {
170
+ req,
171
+ }
172
+ );
173
+ if (rendered)
174
+ responses.push(
175
+ wrapSegment(
176
+ wrapCard(
177
+ tool.skill.skill_label || tool.skill.constructor.skill_name,
178
+ rendered
179
+ ),
180
+ agent_label
181
+ )
182
+ );
183
+ }
184
+ }
185
+ if (answer.content && !answer.tool_calls)
186
+ responses.push(wrapSegment(md.render(answer.content), agent_label));
187
+ }
139
188
  await addToContext(run, {
140
189
  interactions:
141
190
  typeof answer === "object" && answer.tool_calls
@@ -148,8 +197,6 @@ const process_interaction = async (
148
197
  ]
149
198
  : [{ role: "assistant", content: answer }],
150
199
  });
151
- const responses = [];
152
-
153
200
  if (typeof answer === "object" && answer.tool_calls) {
154
201
  if (answer.content)
155
202
  responses.push(wrapSegment(md.render(answer.content), agent_label));
@@ -212,6 +259,7 @@ const process_interaction = async (
212
259
  {
213
260
  role: "tool",
214
261
  tool_call_id: tool_call.id,
262
+ call_id: tool_call.call_id,
215
263
  name: tool_call.function.name,
216
264
  content:
217
265
  result && typeof result !== "string"
@@ -227,7 +275,8 @@ const process_interaction = async (
227
275
  ...prevResponses,
228
276
  ...responses,
229
277
  ]);
230
- } else responses.push(wrapSegment(md.render(answer), agent_label));
278
+ } else if (typeof answer === "string")
279
+ responses.push(wrapSegment(md.render(answer), agent_label));
231
280
 
232
281
  return {
233
282
  json: {
@@ -250,4 +299,5 @@ module.exports = {
250
299
  wrapCard,
251
300
  wrapSegment,
252
301
  process_interaction,
302
+ find_image_tool,
253
303
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/agents",
3
- "version": "0.2.3",
3
+ "version": "0.3.1",
4
4
  "description": "AI agents for Saltcorn",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -0,0 +1,98 @@
1
+ const { div, pre } = require("@saltcorn/markup/tags");
2
+ const Workflow = require("@saltcorn/data/models/workflow");
3
+ const Form = require("@saltcorn/data/models/form");
4
+ const Table = require("@saltcorn/data/models/table");
5
+ const File = require("@saltcorn/data/models/file");
6
+ const View = require("@saltcorn/data/models/view");
7
+ const { getState } = require("@saltcorn/data/db/state");
8
+ const db = require("@saltcorn/data/db");
9
+ const { eval_expression } = require("@saltcorn/data/models/expression");
10
+ const { interpolate } = require("@saltcorn/data/utils");
11
+
12
+ class GenerateImage {
13
+ static skill_name = "Image generation";
14
+
15
+ get skill_label() {
16
+ return `Image generation`;
17
+ }
18
+
19
+ constructor(cfg) {
20
+ Object.assign(this, cfg);
21
+ }
22
+
23
+ static async configFields() {
24
+ return [
25
+ {
26
+ name: "quality",
27
+ label: "Quality",
28
+ type: "String",
29
+ required: true,
30
+ attributes: { options: ["auto", "low", "medium", "high"] },
31
+ },
32
+ {
33
+ name: "size",
34
+ label: "Size",
35
+ type: "String",
36
+ required: true,
37
+ attributes: {
38
+ options: ["auto", "1024x1024", "1536x1024", "1024x1536"],
39
+ },
40
+ },
41
+ {
42
+ name: "format",
43
+ label: "Format",
44
+ type: "String",
45
+ required: true,
46
+ attributes: { options: ["png", "jpeg", "webp"] },
47
+ },
48
+ {
49
+ name: "transparent",
50
+ label: "Transparent",
51
+ type: "Bool",
52
+ },
53
+ {
54
+ name: "save_file",
55
+ label: "Save file",
56
+ type: "String",
57
+ required: true,
58
+ attributes: { options: ["Always", "Never"] }, //, "Button"] },
59
+ },
60
+ ];
61
+ }
62
+
63
+ provideTools() {
64
+ const tool = {
65
+ type: "image_generation",
66
+ size: this.size,
67
+ quality: this.quality,
68
+ process: async (v, { req }) => {
69
+ if (this.save_file === "Always") {
70
+ const buf = Buffer.from(v.result, "base64");
71
+ const file = await File.from_contents(
72
+ `genimg.${v.output_format}`,
73
+ `image/${v.output_format}`,
74
+ buf,
75
+ req?.user?.id,
76
+ 100
77
+ );
78
+ return { filename: file.path_to_serve, result: null };
79
+ }
80
+ },
81
+ renderToolResponse: (v) => {
82
+ const [ws, hs] = v.size.split("x");
83
+ if (v.filename)
84
+ return `<img height="${+hs / 4}" width="${
85
+ +ws / 4
86
+ }" src="/files/serve/${v.filename}" />`;
87
+ else
88
+ return `<img height="${+hs / 4}" width="${+ws / 4}" src="data:image/${
89
+ v.output_format
90
+ };base64, ${v.result}" />`;
91
+ },
92
+ };
93
+ if (this.transparent) tool.background = "transparent";
94
+ return tool;
95
+ }
96
+ }
97
+
98
+ module.exports = GenerateImage;
@@ -0,0 +1,91 @@
1
+ const { div, pre } = require("@saltcorn/markup/tags");
2
+ const Workflow = require("@saltcorn/data/models/workflow");
3
+ const Form = require("@saltcorn/data/models/form");
4
+ const Table = require("@saltcorn/data/models/table");
5
+ const View = require("@saltcorn/data/models/view");
6
+ const { getState } = require("@saltcorn/data/db/state");
7
+ const db = require("@saltcorn/data/db");
8
+ const { eval_expression } = require("@saltcorn/data/models/expression");
9
+ const { interpolate } = require("@saltcorn/data/utils");
10
+
11
+ class PreloadData {
12
+ static skill_name = "Preload Data";
13
+
14
+ get skill_label() {
15
+ return `Preload Data`;
16
+ }
17
+
18
+ constructor(cfg) {
19
+ Object.assign(this, cfg);
20
+ }
21
+
22
+ async systemPrompt({ user }) {
23
+ const prompts = [];
24
+ if (this.add_sys_prompt) prompts.push(this.add_sys_prompt);
25
+ const table = Table.findOne(this.table_name);
26
+ const q = eval_expression(
27
+ this.preload_query,
28
+ {},
29
+ user,
30
+ "PreloadData query"
31
+ );
32
+
33
+ const rows = await table.getRows(q);
34
+ if (this.contents_expr) {
35
+ for (const row of rows)
36
+ prompts.push(interpolate(this.contents_expr, row, user));
37
+ } else {
38
+ const hidden_fields = this.hidden_fields.split(",").map((s) => s.trim());
39
+ for (const row of rows) {
40
+ hidden_fields.forEach((k) => {
41
+ delete row[k];
42
+ });
43
+ prompts.push(JSON.stringify(row));
44
+ }
45
+ }
46
+ return prompts.join("\n");
47
+ }
48
+
49
+ static async configFields() {
50
+ const allTables = await Table.find();
51
+
52
+ return [
53
+ {
54
+ name: "table_name",
55
+ label: "Table",
56
+ sublabel: "Which table to search",
57
+ type: "String",
58
+ required: true,
59
+ attributes: { options: allTables.map((t) => t.name) },
60
+ },
61
+ {
62
+ name: "preload_query",
63
+ label: "Query",
64
+ type: "String",
65
+ class: "validate-expression",
66
+ },
67
+ {
68
+ name: "add_sys_prompt",
69
+ label: "Additional prompt",
70
+ type: "String",
71
+ fieldview: "textarea",
72
+ },
73
+ {
74
+ name: "contents_expr",
75
+ label: "Contents string",
76
+ type: "String",
77
+ sublabel:
78
+ "Use handlebars (<code>{{ }}</code>) to access fields in each retrieved row",
79
+ },
80
+ {
81
+ name: "hidden_fields",
82
+ label: "Hide fields",
83
+ type: "String",
84
+ sublabel:
85
+ "Comma-separated list of fields to hide from the prompt, if not using the contents string",
86
+ },
87
+ ];
88
+ }
89
+ }
90
+
91
+ module.exports = PreloadData;