@saltcorn/agents 0.4.3 → 0.4.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/agent-view.js CHANGED
@@ -43,6 +43,7 @@ const {
43
43
  find_image_tool,
44
44
  is_debug_mode,
45
45
  get_initial_interactions,
46
+ get_skill_instances,
46
47
  } = require("./common");
47
48
  const MarkdownIt = require("markdown-it"),
48
49
  md = new MarkdownIt();
@@ -86,6 +87,12 @@ const configuration_workflow = (req) =>
86
87
  name: "prev_runs_closed",
87
88
  label: "Initially closed",
88
89
  type: "Bool",
90
+ showIf: { show_prev_runs: true },
91
+ },
92
+ {
93
+ name: "stream",
94
+ label: "Stream response",
95
+ type: "Bool",
89
96
  },
90
97
  {
91
98
  name: "placeholder",
@@ -161,6 +168,7 @@ const run = async (
161
168
  placeholder,
162
169
  explainer,
163
170
  image_upload,
171
+ stream,
164
172
  },
165
173
  state,
166
174
  { res, req }
@@ -316,11 +324,19 @@ const run = async (
316
324
  }
317
325
  runInteractions = interactMarkups.join("");
318
326
  }
327
+ const skill_form_widgets = [];
328
+ for (const skill of get_skill_instances(action.configuration)) {
329
+ if (skill.formWidget)
330
+ skill_form_widgets.push(
331
+ await skill.formWidget({ user: req.user, klass: "skill-form-widget" })
332
+ );
333
+ }
334
+
319
335
  const debugMode = is_debug_mode(action.configuration, req.user);
320
336
  const input_form = form(
321
337
  {
322
338
  onsubmit: `event.preventDefault();spin_send_button();view_post('${viewname}', 'interact', new FormData(this), processCopilotResponse);return false;`,
323
- class: "form-namespace copilot mt-2",
339
+ class: ["form-namespace copilot mt-2 agent-view"],
324
340
  method: "post",
325
341
  },
326
342
  input({
@@ -364,8 +380,10 @@ const run = async (
364
380
  onclick: "press_agent_debug_button()",
365
381
  class: "debugicon fas fa-bug",
366
382
  }),
383
+ skill_form_widgets,
367
384
  explainer && small({ class: "explainer" }, i(explainer))
368
- )
385
+ ),
386
+ stream && div({ class: "next_response_scratch" })
369
387
  );
370
388
 
371
389
  const prev_runs_side_bar = div(
@@ -449,6 +467,12 @@ const run = async (
449
467
  top: -1.8rem;
450
468
  left: 0.1rem;
451
469
  cursor: pointer;
470
+ }
471
+ .copilot-entry .skill-form-widget {
472
+ position: relative;
473
+ top: -2rem;
474
+ left: 0.4rem;
475
+ display: inline;
452
476
  }
453
477
  .session-open-sessions, .open-prev-runs {
454
478
  cursor: pointer;
@@ -497,6 +521,7 @@ const run = async (
497
521
  if(hadFile)
498
522
  $("#copilotinteractions").append(wrapSegment('File', "You"))
499
523
  $("textarea[name=userinput]").val("")
524
+ $('form.agent-view div.next_response_scratch').html("")
500
525
 
501
526
  if(res.response)
502
527
  $("#copilotinteractions").append(res.response)
@@ -657,7 +682,8 @@ const interact = async (table_id, viewname, config, body, { req, res }) => {
657
682
  req,
658
683
  action.name,
659
684
  [],
660
- triggering_row
685
+ triggering_row,
686
+ config.stream
661
687
  );
662
688
  };
663
689
 
@@ -682,13 +708,22 @@ const debug_info = async (table_id, viewname, config, body, { req, res }) => {
682
708
  if (table) triggering_row = await table.getRow({ [pk]: triggering_row_id });
683
709
  }
684
710
  const run = await WorkflowRun.findOne({ id: +run_id });
685
- const complArgs = await getCompletionArguments(
686
- action.configuration,
687
- req.user,
688
- triggering_row
689
- );
711
+ let sysPrompt = "";
712
+ if (
713
+ run.context.api_interactions?.[0].request?.messages?.[0]?.role === "system"
714
+ ) {
715
+ sysPrompt =
716
+ run.context.api_interactions?.[0].request?.messages?.[0].content;
717
+ } else {
718
+ const complArgs = await getCompletionArguments(
719
+ action.configuration,
720
+ req.user,
721
+ triggering_row
722
+ );
723
+ sysPrompt = complArgs.systemPrompt;
724
+ }
690
725
  const debug_html = div(
691
- div(h4("System prompt"), pre(complArgs.systemPrompt)),
726
+ div(h4("System prompt"), pre(sysPrompt)),
692
727
  div(
693
728
  h4("API interactions"),
694
729
  pre(JSON.stringify(run.context.api_interactions, null, 2))
package/common.js CHANGED
@@ -2,6 +2,7 @@ const { getState } = require("@saltcorn/data/db/state");
2
2
  const { div, span } = require("@saltcorn/markup/tags");
3
3
  const Trigger = require("@saltcorn/data/models/trigger");
4
4
  const { interpolate } = require("@saltcorn/data/utils");
5
+ const db = require("@saltcorn/data/db");
5
6
 
6
7
  const MarkdownIt = require("markdown-it"),
7
8
  md = new MarkdownIt();
@@ -31,6 +32,7 @@ const get_skills = () => {
31
32
  require("./skills/PreloadData"),
32
33
  require("./skills/GenerateImage"),
33
34
  require("./skills/ModelContextProtocol"),
35
+ require("./skills/PromptPicker"),
34
36
  //require("./skills/AdaptiveFeedback"),
35
37
  ...exchange_skills,
36
38
  ];
@@ -89,7 +91,12 @@ const get_initial_interactions = async (config, user, triggering_row) => {
89
91
  return interacts;
90
92
  };
91
93
 
92
- const getCompletionArguments = async (config, user, triggering_row) => {
94
+ const getCompletionArguments = async (
95
+ config,
96
+ user,
97
+ triggering_row,
98
+ formbody
99
+ ) => {
93
100
  let tools = [];
94
101
 
95
102
  let sysPrompts = [
@@ -98,7 +105,11 @@ const getCompletionArguments = async (config, user, triggering_row) => {
98
105
 
99
106
  const skills = get_skill_instances(config);
100
107
  for (const skill of skills) {
101
- const sysPr = await skill.systemPrompt?.({ user, triggering_row });
108
+ const sysPr = await skill.systemPrompt?.({
109
+ ...(formbody || {}),
110
+ user,
111
+ triggering_row,
112
+ });
102
113
  if (sysPr) sysPrompts.push(sysPr);
103
114
  const skillTools = skill.provideTools?.();
104
115
  if (skillTools && Array.isArray(skillTools)) tools.push(...skillTools);
@@ -190,12 +201,15 @@ const process_interaction = async (
190
201
  req,
191
202
  agent_label = "Copilot",
192
203
  prevResponses = [],
193
- triggering_row = {}
204
+ triggering_row = {},
205
+ stream = false
194
206
  ) => {
207
+ const sysState = getState();
195
208
  const complArgs = await getCompletionArguments(
196
209
  config,
197
210
  req.user,
198
- triggering_row
211
+ triggering_row,
212
+ req.body
199
213
  );
200
214
  complArgs.chat = run.context.interactions.map(only_response_text_if_present);
201
215
  //complArgs.debugResult = true;
@@ -203,7 +217,26 @@ const process_interaction = async (
203
217
  const debugMode = is_debug_mode(config, req.user);
204
218
  const debugCollector = {};
205
219
  if (debugMode) complArgs.debugCollector = debugCollector;
206
- const answer = await getState().functions.llm_generate.run("", complArgs);
220
+ if (stream && sysState.getConfig("enable_dynamic_updates") && req.user) {
221
+ complArgs.streamCallback = (response) => {
222
+ const content =
223
+ response.choices[0].content || response.choices[0].delta?.content;
224
+ if (content)
225
+ sysState?.emitDynamicUpdate(
226
+ db.getTenantSchema(),
227
+ {
228
+ eval_js: `$('form.agent-view div.next_response_scratch').append(${JSON.stringify(
229
+ content
230
+ )})`,
231
+ },
232
+ [req.user.id]
233
+ );
234
+ };
235
+ }
236
+ const answer = await sysState.functions.llm_generate.run("", complArgs);
237
+
238
+ //console.log({answer});
239
+
207
240
  if (debugMode)
208
241
  await addToContext(run, {
209
242
  api_interactions: [debugCollector],
@@ -263,7 +296,7 @@ const process_interaction = async (
263
296
  let hasResult = false;
264
297
  if ((answer.mcp_calls || []).length && !answer.content) hasResult = true;
265
298
  for (const tool_call of answer.tool_calls || []) {
266
- console.log("call function", tool_call.function);
299
+ console.log("call function", tool_call.function?.name);
267
300
 
268
301
  await addToContext(run, {
269
302
  funcalls: { [tool_call.id]: tool_call.function },
@@ -272,6 +305,24 @@ const process_interaction = async (
272
305
  const tool = find_tool(tool_call.function?.name, config);
273
306
 
274
307
  if (tool) {
308
+ if (
309
+ stream &&
310
+ sysState.getConfig("enable_dynamic_updates") &&
311
+ req.user
312
+ ) {
313
+ let content =
314
+ "Using skill: " + tool.skill.skill_label ||
315
+ tool.skill.constructor.skill_name;
316
+ sysState?.emitDynamicUpdate(
317
+ db.getTenantSchema(),
318
+ {
319
+ eval_js: `$('form.agent-view div.next_response_scratch').append(${JSON.stringify(
320
+ content
321
+ )})`,
322
+ },
323
+ [req.user.id]
324
+ );
325
+ }
275
326
  if (tool.tool.renderToolCall) {
276
327
  const row = JSON.parse(tool_call.function.arguments);
277
328
  const rendered = await tool.tool.renderToolCall(row, {
@@ -337,7 +388,8 @@ const process_interaction = async (
337
388
  req,
338
389
  agent_label,
339
390
  [...prevResponses, ...responses],
340
- triggering_row
391
+ triggering_row,
392
+ stream
341
393
  );
342
394
  } else if (typeof answer === "string")
343
395
  responses.push(wrapSegment(md.render(answer), agent_label));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/agents",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "AI agents for Saltcorn",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -11,7 +11,7 @@ const db = require("@saltcorn/data/db");
11
11
  const { eval_expression } = require("@saltcorn/data/models/expression");
12
12
  const { interpolate } = require("@saltcorn/data/utils");
13
13
  const vm = require("vm");
14
- const fetch = require("node-fetch");
14
+ const fetch = global.fetch || require("node-fetch");
15
15
 
16
16
  class PreloadData {
17
17
  static skill_name = "Preload Data";
@@ -69,7 +69,7 @@ class PreloadData {
69
69
  const table = Table.findOne(this.table_name);
70
70
  const q = eval_expression(
71
71
  this.preload_query,
72
- {},
72
+ triggering_row || {},
73
73
  user,
74
74
  "PreloadData query"
75
75
  );
@@ -0,0 +1,51 @@
1
+ const Workflow = require("@saltcorn/data/models/workflow");
2
+ const Form = require("@saltcorn/data/models/form");
3
+ const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
4
+ const { select, option } = require("@saltcorn/markup/tags");
5
+ const { eval_expression } = require("@saltcorn/data/models/expression");
6
+ const { validID } = require("@saltcorn/markup/layout_utils");
7
+
8
+ class PromptPicker {
9
+ static skill_name = "Prompt picker";
10
+ get skill_label() {
11
+ return `Prompt picker`;
12
+ }
13
+ constructor(cfg) {
14
+ Object.assign(this, cfg);
15
+ this.options = eval_expression(
16
+ this.options_obj,
17
+ {},
18
+ null,
19
+ "Prompt picker options"
20
+ );
21
+ this.formname = validID("pp" + Object.keys(this.options));
22
+ }
23
+ static async configFields() {
24
+ return [
25
+ { name: "placeholder", label: "Placeholder", type: "String" },
26
+ {
27
+ name: "options_obj",
28
+ label: "System prompt contents",
29
+ sublabel: `JavaScript object where the keys are the options and values are added to system prompt. Example:<br><code>{"Pirate":"Speak like a pirate", "Pop star":"Speak like a pop star"}</code>`,
30
+ type: "String",
31
+ fieldview: "textarea",
32
+ required: true,
33
+ },
34
+ ];
35
+ }
36
+ async formWidget({ user, klass }) {
37
+ return select(
38
+ {
39
+ class: ["form-select form-select-sm w-unset", klass],
40
+ name: this.formname,
41
+ },
42
+ this.placeholder && option({ disabled: true }, this.placeholder),
43
+ Object.keys(this.options).map((o) => option(o))
44
+ );
45
+ }
46
+ systemPrompt(body) {
47
+ if (body[this.formname]) return this.options[body[this.formname]];
48
+ }
49
+ }
50
+
51
+ module.exports = PromptPicker;