@saltcorn/agents 0.7.2 → 0.7.3

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/common.js CHANGED
@@ -34,9 +34,11 @@ const get_skills = () => {
34
34
  require("./skills/GenerateImage"),
35
35
  require("./skills/ModelContextProtocol"),
36
36
  require("./skills/PromptPicker"),
37
+ require("./skills/ModelPicker"),
37
38
  require("./skills/RunJsCode"),
38
39
  require("./skills/GenerateAndRunJsCode"),
39
40
  require("./skills/Fetch"),
41
+ require("./skills/WebSearch"),
40
42
  require("./skills/Subagent"),
41
43
  //require("./skills/AdaptiveFeedback"),
42
44
  ...exchange_skills,
@@ -127,12 +129,21 @@ const getCompletionArguments = async (
127
129
  ];
128
130
 
129
131
  const skills = get_skill_instances(config);
132
+ const overrides = {};
130
133
  for (const skill of skills) {
131
134
  const sysPr = await skill.systemPrompt?.({
132
135
  ...(formbody || {}),
133
136
  user,
134
137
  triggering_row,
135
138
  });
139
+ const overide =
140
+ (await skill.settingsOverride?.({
141
+ ...(formbody || {}),
142
+ user,
143
+ triggering_row,
144
+ })) || {};
145
+ Object.assign(overrides, overide);
146
+
136
147
  if (sysPr) sysPrompts.push(sysPr);
137
148
  const skillTools = skill.provideTools?.();
138
149
  if (skillTools && Array.isArray(skillTools)) tools.push(...skillTools);
@@ -141,7 +152,9 @@ const getCompletionArguments = async (
141
152
  if (tools.length === 0) tools = undefined;
142
153
  const complArgs = { tools, systemPrompt: sysPrompts.join("\n\n") };
143
154
  if (config.model) complArgs.model = config.model;
144
- if (config.alt_config) complArgs.alt_config = config.alt_config;
155
+
156
+ if (overrides.alt_config || config.alt_config)
157
+ complArgs.alt_config = overrides.alt_config || config.alt_config;
145
158
  return complArgs;
146
159
  };
147
160
 
@@ -237,6 +250,7 @@ const process_interaction = async (
237
250
  );
238
251
  complArgs.appendToChat = true;
239
252
  complArgs.chat = run.context.interactions;
253
+ const use_alt_config = complArgs.alt_config;
240
254
  //complArgs.debugResult = true;
241
255
  //console.log("complArgs", JSON.stringify(complArgs, null, 2));
242
256
  const debugMode = is_debug_mode(config, req.user);
@@ -344,6 +358,7 @@ const process_interaction = async (
344
358
  //const actions = [];
345
359
  let hasResult = false;
346
360
  if ((answer.mcp_calls || []).length && !answer.content) hasResult = true;
361
+ const toolResults = {};
347
362
  if (answer.hasToolCalls)
348
363
  for (const tool_call of answer.getToolCalls()) {
349
364
  getState().log(6, "call function " + tool_call.tool_name);
@@ -395,6 +410,7 @@ const process_interaction = async (
395
410
  const result = await tool.tool.process(tool_call.input, {
396
411
  req,
397
412
  });
413
+ toolResults[tool_call.tool_call_id] = result;
398
414
  if (result.stop) stop = true;
399
415
  if (
400
416
  (typeof result === "object" && Object.keys(result || {}).length) ||
@@ -440,7 +456,21 @@ const process_interaction = async (
440
456
  await addToContext(run, {
441
457
  interactions: run.context.interactions,
442
458
  });
459
+
460
+ if (myHasResult && !stop) hasResult = true;
461
+ }
462
+ }
463
+
464
+ // postprocess - now all tool calls have responses
465
+ if (answer.hasToolCalls)
466
+ for (const tool_call of answer.getToolCalls()) {
467
+ const tool = find_tool(tool_call.tool_name, config);
468
+ if (tool) {
469
+ let stop = false,
470
+ myHasResult = false;
443
471
  if (tool.tool.postProcess && !stop) {
472
+ let result = toolResults[tool_call.tool_call_id];
473
+
444
474
  const chat = run.context.interactions;
445
475
  let generateUsed = false;
446
476
  const systemPrompt = await getSystemPrompt(
@@ -461,7 +491,7 @@ const process_interaction = async (
461
491
  chat,
462
492
  appendToChat: true,
463
493
  systemPrompt,
464
- alt_config: config.alt_config,
494
+ alt_config: use_alt_config,
465
495
  ...opts,
466
496
  });
467
497
  },
@@ -490,9 +520,10 @@ const process_interaction = async (
490
520
  ],
491
521
  });
492
522
  if (postprocres.add_response) {
493
- const renderedAddResponse = typeof postprocres.add_response === "string"
494
- ? md.render(postprocres.add_response)
495
- : postprocres.add_response;
523
+ const renderedAddResponse =
524
+ typeof postprocres.add_response === "string"
525
+ ? md.render(postprocres.add_response)
526
+ : postprocres.add_response;
496
527
  add_response(
497
528
  wrapSegment(
498
529
  wrapCard(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/agents",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "description": "AI agents for Saltcorn",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -0,0 +1,47 @@
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
+ const { getState } = require("@saltcorn/data/db/state");
8
+
9
+ class ModelPicker {
10
+ static skill_name = "Model picker";
11
+ get skill_label() {
12
+ return `Model picker`;
13
+ }
14
+ constructor(cfg) {
15
+ Object.assign(this, cfg);
16
+ }
17
+ static async configFields() {
18
+ return [
19
+ { name: "placeholder", label: "Placeholder", type: "String" },
20
+ {
21
+ name: "default_label",
22
+ label: "Default configuration label",
23
+ type: "String",
24
+ },
25
+ ];
26
+ }
27
+ async formWidget({ user, klass }) {
28
+ const llm_cfg_fun = getState().functions.llm_get_configuration;
29
+ const alt_config_options = llm_cfg_fun
30
+ ? llm_cfg_fun.run().alt_config_names || []
31
+ : [];
32
+ return select(
33
+ {
34
+ class: ["form-select form-select-sm w-unset", klass],
35
+ name: "modelpicker",
36
+ },
37
+ this.placeholder && option({ disabled: true }, this.placeholder),
38
+ option({ value: "" }, this.default_label),
39
+ alt_config_options.map((o) => option(o)),
40
+ );
41
+ }
42
+ settingsOverride(body) {
43
+ if (body.modelpicker) return { alt_config: body.modelpicker };
44
+ }
45
+ }
46
+
47
+ module.exports = ModelPicker;
@@ -0,0 +1,96 @@
1
+ const { div, pre, a } = 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 User = require("@saltcorn/data/models/user");
6
+ const File = require("@saltcorn/data/models/file");
7
+ const View = require("@saltcorn/data/models/view");
8
+ const Trigger = require("@saltcorn/data/models/trigger");
9
+ const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
10
+ const { getState } = require("@saltcorn/data/db/state");
11
+ const db = require("@saltcorn/data/db");
12
+ const { eval_expression } = require("@saltcorn/data/models/expression");
13
+ const { interpolate, sleep } = require("@saltcorn/data/utils");
14
+ const { features } = require("@saltcorn/data/db/state");
15
+ const { button } = require("@saltcorn/markup/tags");
16
+ const { validID } = require("@saltcorn/markup/layout_utils");
17
+
18
+ const vm = require("vm");
19
+
20
+ //const { fieldProperties } = require("./helpers");
21
+
22
+ class WebSearchSkill {
23
+ static skill_name = "Web search";
24
+
25
+ get skill_label() {
26
+ return "Web search";
27
+ }
28
+
29
+ constructor(cfg) {
30
+ Object.assign(this, cfg);
31
+ }
32
+
33
+ static async configFields() {
34
+ return [
35
+ {
36
+ name: "search_provider",
37
+ label: "Search provider",
38
+ type: "String",
39
+ required: true,
40
+ attributes: { options: ["By URL template"] },
41
+ },
42
+ {
43
+ name: "url_template",
44
+ label: "URL template",
45
+ sublabel: "Use <code>{{q}}</code> for the URL-encoded search phrase",
46
+ type: "String",
47
+ required: true,
48
+ showIf: { search_provider: "By URL template" },
49
+ },
50
+ {
51
+ name: "header",
52
+ label: "Header",
53
+ sublabel: "For example <code>X-Subscription-Token: YOUR_API_KEY</code>",
54
+ type: "String",
55
+ showIf: { search_provider: "By URL template" },
56
+ },
57
+ ];
58
+ }
59
+ systemPrompt() {
60
+ return "If you need to search the web with a search phrase, use the web_search to get search engine results";
61
+ }
62
+
63
+ provideTools = () => {
64
+ return {
65
+ type: "function",
66
+ process: async (row) => {
67
+ const fOpts = { method: "GET" };
68
+ if (this.header) {
69
+ const [key, val] = this.header.split(":");
70
+ const myHeaders = new Headers();
71
+ myHeaders.append(key, val.trim());
72
+ fOpts.headers = myHeaders;
73
+ }
74
+ const url = interpolate(this.url_template, { q: row.search_phrase });
75
+ const resp = await fetch(url);
76
+ return await resp.text();
77
+ },
78
+ function: {
79
+ name: "web_search",
80
+ description: "Search the web with a search engine by a search phrase",
81
+ parameters: {
82
+ type: "object",
83
+ required: ["search_phrase"],
84
+ properties: {
85
+ search_phrase: {
86
+ description: "The phrase to search for",
87
+ type: "string",
88
+ },
89
+ },
90
+ },
91
+ },
92
+ };
93
+ };
94
+ }
95
+
96
+ module.exports = WebSearchSkill;
@@ -29,6 +29,13 @@ beforeAll(async () => {
29
29
  when_trigger: "Never",
30
30
  configuration: require("./agentcfg").maths_agent_cfg,
31
31
  });
32
+ await Trigger.create({
33
+ name: "OracleAgent",
34
+ description: "The all-knowing oracle answers any question",
35
+ action: "Agent",
36
+ when_trigger: "Never",
37
+ configuration: require("./agentcfg").oracle_agent_cfg,
38
+ });
32
39
 
33
40
  await getState().refresh_triggers(false);
34
41
  //await getState().setConfig("log_level", 6);
@@ -109,6 +116,27 @@ for (const nameconfig of require("./configs")) {
109
116
  });
110
117
  expect(result.json.response).toContain("987");
111
118
  });
119
+ it("run multiple subagents concurrenty", async () => {
120
+ const configuration = { ...require("./agentcfg").agent1 };
121
+ configuration.skills = [
122
+ ...configuration.skills,
123
+ {
124
+ agent_name: "OracleAgent",
125
+ skill_type: "Subagent",
126
+ },
127
+ ];
128
+ const result = await action.run({
129
+ row: {
130
+ theprompt:
131
+ "What is the 16th Fibonacci number (when F1=1 and F2=1)? Consult both the math agent and the oracle and see if they agree",
132
+ },
133
+ configuration,
134
+ user,
135
+ req: { user },
136
+ });
137
+ expect(result.json.response).toContain("987");
138
+ });
112
139
  });
140
+
113
141
  //break;
114
142
  }
package/tests/agentcfg.js CHANGED
@@ -65,4 +65,26 @@ const maths_agent_cfg = {
65
65
  "If the user asks an arithmetic question, generate javascript code to solve it with the generate_arithmetic_code tool",
66
66
  };
67
67
 
68
- module.exports = { agent1, maths_agent_cfg };
68
+ const oracle_agent_cfg = {
69
+ model: "",
70
+ prompt: "{{theprompt}}",
71
+ skills: [
72
+ {
73
+ tool_name: "ask_the_oracle",
74
+ tool_description: "Ask the all-knowing oracle a question",
75
+ skill_type: "Run JavaScript code",
76
+ js_code: "return '987';",
77
+ toolargs: [
78
+ {
79
+ name: "question",
80
+ description: "The question you would like to ask",
81
+ argtype: "string",
82
+ },
83
+ ],
84
+ },
85
+ ],
86
+ sys_prompt:
87
+ "You can ask a question of the all-knowing oracle by calling the ask_the_oracle tool ",
88
+ };
89
+
90
+ module.exports = { agent1, maths_agent_cfg, oracle_agent_cfg };