@saltcorn/agents 0.3.5 → 0.4.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/agent-view.js CHANGED
@@ -17,6 +17,7 @@ const {
17
17
  code,
18
18
  input,
19
19
  h4,
20
+ h3,
20
21
  style,
21
22
  h5,
22
23
  button,
@@ -40,6 +41,7 @@ const {
40
41
  wrapSegment,
41
42
  process_interaction,
42
43
  find_image_tool,
44
+ is_debug_mode,
43
45
  } = require("./common");
44
46
  const MarkdownIt = require("markdown-it"),
45
47
  md = new MarkdownIt();
@@ -112,7 +114,16 @@ const configuration_workflow = (req) =>
112
114
  ],
113
115
  });
114
116
 
115
- const get_state_fields = () => [];
117
+ const get_state_fields = async (table_id) =>
118
+ table_id
119
+ ? [
120
+ {
121
+ name: "id",
122
+ type: "Integer",
123
+ primary_key: true,
124
+ },
125
+ ]
126
+ : [];
116
127
 
117
128
  const uploadForm = (viewname, req) =>
118
129
  span(
@@ -152,6 +163,14 @@ const run = async (
152
163
  const cfgMsg = incompleteCfgMsg();
153
164
  if (cfgMsg) return cfgMsg;
154
165
  let runInteractions = "";
166
+ let triggering_row_id;
167
+ if (table_id) {
168
+ const table = Table.findOne(table_id);
169
+ const pk = table?.pk_name;
170
+ if (table && state[pk])
171
+ //triggering_row = await table.getRow({ [pk]: state[pk] });
172
+ triggering_row_id = state[pk];
173
+ }
155
174
  if (state.run_id) {
156
175
  const run = prevRuns.find((r) => r.id == state.run_id);
157
176
  const interactMarkups = [];
@@ -187,7 +206,6 @@ const run = async (
187
206
  break;
188
207
  case "assistant":
189
208
  case "system":
190
-
191
209
  for (const tool_call of interact.tool_calls || []) {
192
210
  const toolSkill = find_tool(
193
211
  tool_call.function?.name,
@@ -214,7 +232,6 @@ const run = async (
214
232
  }
215
233
  }
216
234
  for (const image_call of interact.content?.image_calls || []) {
217
-
218
235
  const toolSkill = find_image_tool(action.configuration);
219
236
  if (toolSkill) {
220
237
  if (toolSkill.tool.renderToolResponse) {
@@ -285,6 +302,7 @@ const run = async (
285
302
  }
286
303
  runInteractions = interactMarkups.join("");
287
304
  }
305
+ const debugMode = is_debug_mode(action.configuration, req.user);
288
306
  const input_form = form(
289
307
  {
290
308
  onsubmit: `event.preventDefault();spin_send_button();view_post('${viewname}', 'interact', new FormData(this), processCopilotResponse);return false;`,
@@ -302,6 +320,12 @@ const run = async (
302
320
  name: "run_id",
303
321
  value: state.run_id ? +state.run_id : undefined,
304
322
  }),
323
+ input({
324
+ type: "hidden",
325
+ class: "form-control ",
326
+ name: "triggering_row_id",
327
+ value: triggering_row_id || "",
328
+ }),
305
329
  div(
306
330
  { class: "copilot-entry" },
307
331
  textarea({
@@ -318,6 +342,11 @@ const run = async (
318
342
  i({ id: "sendbuttonicon", class: "far fa-paper-plane" })
319
343
  ),
320
344
  image_upload && uploadForm(viewname, req),
345
+ debugMode &&
346
+ i({
347
+ onclick: "press_agent_debug_button()",
348
+ class: "debugicon fas fa-bug",
349
+ }),
321
350
  explainer && small({ class: "explainer" }, i(explainer))
322
351
  )
323
352
  );
@@ -380,6 +409,12 @@ const run = async (
380
409
  position: relative;
381
410
  top: -1.8rem;
382
411
  left: 0.1rem;
412
+ }
413
+ .copilot-entry .debugicon {
414
+ position: relative;
415
+ top: -1.8rem;
416
+ left: 0.1rem;
417
+ cursor: pointer;
383
418
  }
384
419
  .copilot-entry span.attach_agent_image_wrap {
385
420
  position: relative;
@@ -425,6 +460,21 @@ const run = async (
425
460
  btn.css({ width: "" }).prop("disabled", false);
426
461
  btn.removeData("old-text");
427
462
  }
463
+ function press_agent_debug_button() {
464
+ const $runidin= $("input[name=run_id")
465
+ const runid = $runidin.val()
466
+ if(runid)
467
+ view_post('${viewname}', 'debug_info', {run_id:runid, triggering_row_id:$("input[name=triggering_row_id").val()}, show_agent_debug_info)
468
+ }
469
+ function show_agent_debug_info(info) {
470
+ ensure_modal_exists_and_closed();
471
+ $("#scmodal .modal-body").html(info.debug_html);
472
+ $("#scmodal .modal-title").html(decodeURIComponent("Agent session information"));
473
+ $(".modal-dialog").css("min-width", "80%");
474
+ new bootstrap.Modal($("#scmodal"), {
475
+ focus: false,
476
+ }).show();
477
+ }
428
478
  function delprevrun(e, runid) {
429
479
  e.preventDefault();
430
480
  e.stopPropagation();
@@ -479,8 +529,14 @@ const run = async (
479
529
  };
480
530
 
481
531
  const interact = async (table_id, viewname, config, body, { req, res }) => {
482
- const { userinput, run_id } = body;
532
+ const { userinput, run_id, triggering_row_id } = body;
483
533
  let run;
534
+ let triggering_row;
535
+ if (table_id && triggering_row_id) {
536
+ const table = Table.findOne(table_id);
537
+ const pk = table?.pk_name;
538
+ if (table) triggering_row = await table.getRow({ [pk]: triggering_row_id });
539
+ }
484
540
  if (!run_id || run_id === "undefined")
485
541
  run = await WorkflowRun.create({
486
542
  status: "Running",
@@ -490,6 +546,7 @@ const interact = async (table_id, viewname, config, body, { req, res }) => {
490
546
  implemented_fcall_ids: [],
491
547
  interactions: [{ role: "user", content: userinput }],
492
548
  funcalls: {},
549
+ triggering_row_id,
493
550
  },
494
551
  });
495
552
  else {
@@ -535,7 +592,14 @@ const interact = async (table_id, viewname, config, body, { req, res }) => {
535
592
  }
536
593
  const action = await Trigger.findOne({ id: config.action_id });
537
594
 
538
- return await process_interaction(run, action.configuration, req, action.name);
595
+ return await process_interaction(
596
+ run,
597
+ action.configuration,
598
+ req,
599
+ action.name,
600
+ [],
601
+ triggering_row
602
+ );
539
603
  };
540
604
 
541
605
  const delprevrun = async (table_id, viewname, config, body, { req, res }) => {
@@ -549,6 +613,38 @@ const delprevrun = async (table_id, viewname, config, body, { req, res }) => {
549
613
  return;
550
614
  };
551
615
 
616
+ const debug_info = async (table_id, viewname, config, body, { req, res }) => {
617
+ const { run_id, triggering_row_id } = body;
618
+ const action = await Trigger.findOne({ id: config.action_id });
619
+ let triggering_row;
620
+ if (table_id && triggering_row_id) {
621
+ const table = Table.findOne(table_id);
622
+ const pk = table?.pk_name;
623
+ if (table) triggering_row = await table.getRow({ [pk]: triggering_row_id });
624
+ }
625
+ const run = await WorkflowRun.findOne({ id: +run_id });
626
+ const complArgs = await getCompletionArguments(
627
+ action.configuration,
628
+ req.user,
629
+ triggering_row
630
+ );
631
+ const debug_html = div(
632
+ div(h4("System prompt"), pre(complArgs.systemPrompt)),
633
+ div(
634
+ h4("API interactions"),
635
+ pre(JSON.stringify(run.context.api_interactions, null, 2))
636
+ )
637
+ );
638
+ if (run && req.user?.role_id === 1)
639
+ return {
640
+ json: {
641
+ success: "ok",
642
+ debug_html,
643
+ },
644
+ };
645
+
646
+ return;
647
+ };
552
648
  const wrapAction = (
553
649
  inner_markup,
554
650
  viewname,
@@ -585,7 +681,8 @@ module.exports = {
585
681
  configuration_workflow,
586
682
  display_state_form: false,
587
683
  get_state_fields,
588
- tableless: true,
684
+ //tableless: true,
685
+ table_optional: true,
589
686
  run,
590
- routes: { interact, delprevrun },
687
+ routes: { interact, delprevrun, debug_info },
591
688
  };
package/common.js CHANGED
@@ -1,11 +1,28 @@
1
1
  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
+ const { interpolate } = require("@saltcorn/data/utils");
4
5
 
5
6
  const MarkdownIt = require("markdown-it"),
6
7
  md = new MarkdownIt();
7
8
 
9
+ const nubBy = (f, xs) => {
10
+ const vs = new Set();
11
+ return xs.filter((x) => {
12
+ const y = f(x);
13
+ if (vs.has(y)) return false;
14
+ vs.add(y);
15
+ return true;
16
+ });
17
+ };
18
+
8
19
  const get_skills = () => {
20
+ const state = getState();
21
+ const exchange_skills = nubBy(
22
+ (c) => c.constructor.name,
23
+ state.exchange?.agent_skills || []
24
+ );
25
+
9
26
  return [
10
27
  require("./skills/FTSRetrieval"),
11
28
  require("./skills/EmbeddingRetrieval"),
@@ -15,6 +32,7 @@ const get_skills = () => {
15
32
  require("./skills/GenerateImage"),
16
33
  require("./skills/ModelContextProtocol"),
17
34
  //require("./skills/AdaptiveFeedback"),
35
+ ...exchange_skills,
18
36
  ];
19
37
  };
20
38
 
@@ -61,14 +79,16 @@ const find_image_tool = (config) => {
61
79
  }
62
80
  };
63
81
 
64
- const getCompletionArguments = async (config, user) => {
82
+ const getCompletionArguments = async (config, user, triggering_row) => {
65
83
  let tools = [];
66
84
 
67
- let sysPrompts = [config.sys_prompt];
85
+ let sysPrompts = [
86
+ interpolate(config.sys_prompt, triggering_row || {}, user, "System prompt"),
87
+ ];
68
88
 
69
89
  const skills = get_skill_instances(config);
70
90
  for (const skill of skills) {
71
- const sysPr = await skill.systemPrompt?.({ user });
91
+ const sysPr = await skill.systemPrompt?.({ user, triggering_row });
72
92
  if (sysPr) sysPrompts.push(sysPr);
73
93
  const skillTools = skill.provideTools?.();
74
94
  if (skillTools && Array.isArray(skillTools)) tools.push(...skillTools);
@@ -156,21 +176,32 @@ const only_response_text_if_present = (interact) => {
156
176
  return interact;
157
177
  };
158
178
 
179
+ const is_debug_mode = (config, user) => user?.role_id === 1;
180
+
159
181
  const process_interaction = async (
160
182
  run,
161
183
  config,
162
184
  req,
163
185
  agent_label = "Copilot",
164
- prevResponses = []
186
+ prevResponses = [],
187
+ triggering_row = {}
165
188
  ) => {
166
- const complArgs = await getCompletionArguments(config, req.user);
189
+ const complArgs = await getCompletionArguments(
190
+ config,
191
+ req.user,
192
+ triggering_row
193
+ );
167
194
  complArgs.chat = run.context.interactions.map(only_response_text_if_present);
168
195
  //complArgs.debugResult = true;
169
196
  //console.log("complArgs", JSON.stringify(complArgs, null, 2));
170
-
197
+ const debugMode = is_debug_mode(config, req.user);
198
+ const debugCollector = {};
199
+ if (debugMode) complArgs.debugCollector = debugCollector;
171
200
  const answer = await getState().functions.llm_generate.run("", complArgs);
172
- console.log("answer", answer);
173
-
201
+ if (debugMode)
202
+ await addToContext(run, {
203
+ api_interactions: [debugCollector],
204
+ });
174
205
  const responses = [];
175
206
  if (answer && typeof answer === "object" && answer.image_calls) {
176
207
  for (const image_call of answer.image_calls) {
@@ -294,10 +325,14 @@ const process_interaction = async (
294
325
  }
295
326
  }
296
327
  if (hasResult)
297
- return await process_interaction(run, config, req, agent_label, [
298
- ...prevResponses,
299
- ...responses,
300
- ]);
328
+ return await process_interaction(
329
+ run,
330
+ config,
331
+ req,
332
+ agent_label,
333
+ [...prevResponses, ...responses],
334
+ triggering_row
335
+ );
301
336
  } else if (typeof answer === "string")
302
337
  responses.push(wrapSegment(md.render(answer), agent_label));
303
338
 
@@ -323,4 +358,5 @@ module.exports = {
323
358
  wrapSegment,
324
359
  process_interaction,
325
360
  find_image_tool,
361
+ is_debug_mode,
326
362
  };
package/index.js CHANGED
@@ -51,7 +51,8 @@ module.exports = {
51
51
  {
52
52
  name: "sys_prompt",
53
53
  label: "System prompt",
54
- sublabel: "Additional information for the system prompt",
54
+ sublabel:
55
+ "Additional information for the system prompt. Use interpolations <code>{{ }}</code> to access triggering row variables or user",
55
56
  type: "String",
56
57
  fieldview: "textarea",
57
58
  },
@@ -84,7 +85,14 @@ module.exports = {
84
85
  funcalls: {},
85
86
  },
86
87
  });
87
- return await process_interaction(run, configuration, req);
88
+ return await process_interaction(
89
+ run,
90
+ configuration,
91
+ req,
92
+ undefined,
93
+ [],
94
+ row
95
+ );
88
96
  },
89
97
  },
90
98
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/agents",
3
- "version": "0.3.5",
3
+ "version": "0.4.0",
4
4
  "description": "AI agents for Saltcorn",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -74,6 +74,7 @@ class PreloadData {
74
74
  name: "contents_expr",
75
75
  label: "Contents string",
76
76
  type: "String",
77
+ fieldview: "textarea",
77
78
  sublabel:
78
79
  "Use handlebars (<code>{{ }}</code>) to access fields in each retrieved row",
79
80
  },