@saltcorn/agents 0.7.2 → 0.7.4
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/action.js +4 -0
- package/common.js +48 -6
- package/package.json +1 -1
- package/skills/ModelPicker.js +47 -0
- package/skills/Subagent.js +4 -1
- package/skills/WebSearch.js +96 -0
- package/tests/action.test.js +28 -0
- package/tests/agentcfg.js +23 -1
package/action.js
CHANGED
|
@@ -89,6 +89,7 @@ module.exports = {
|
|
|
89
89
|
trigger_id,
|
|
90
90
|
run_id,
|
|
91
91
|
req,
|
|
92
|
+
is_sub_agent,
|
|
92
93
|
...rest
|
|
93
94
|
}) => {
|
|
94
95
|
const userinput = interpolate(configuration.prompt, row, user);
|
|
@@ -114,6 +115,9 @@ module.exports = {
|
|
|
114
115
|
undefined,
|
|
115
116
|
[],
|
|
116
117
|
row,
|
|
118
|
+
{ stream: false },
|
|
119
|
+
false,
|
|
120
|
+
is_sub_agent
|
|
117
121
|
);
|
|
118
122
|
},
|
|
119
123
|
};
|
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
|
-
|
|
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
|
|
|
@@ -226,6 +239,7 @@ const process_interaction = async (
|
|
|
226
239
|
triggering_row = {},
|
|
227
240
|
agentsViewCfg = { stream: false },
|
|
228
241
|
dyn_updates = false,
|
|
242
|
+
is_sub_agent = false,
|
|
229
243
|
) => {
|
|
230
244
|
const { stream, viewname, layout } = agentsViewCfg;
|
|
231
245
|
const sysState = getState();
|
|
@@ -237,6 +251,7 @@ const process_interaction = async (
|
|
|
237
251
|
);
|
|
238
252
|
complArgs.appendToChat = true;
|
|
239
253
|
complArgs.chat = run.context.interactions;
|
|
254
|
+
const use_alt_config = complArgs.alt_config;
|
|
240
255
|
//complArgs.debugResult = true;
|
|
241
256
|
//console.log("complArgs", JSON.stringify(complArgs, null, 2));
|
|
242
257
|
const debugMode = is_debug_mode(config, req.user);
|
|
@@ -276,6 +291,7 @@ const process_interaction = async (
|
|
|
276
291
|
interactions: complArgs.chat,
|
|
277
292
|
});
|
|
278
293
|
const responses = [];
|
|
294
|
+
const raw_responses = [];
|
|
279
295
|
|
|
280
296
|
const add_response = async (resp, not_final) => {
|
|
281
297
|
if (dyn_updates)
|
|
@@ -344,6 +360,7 @@ const process_interaction = async (
|
|
|
344
360
|
//const actions = [];
|
|
345
361
|
let hasResult = false;
|
|
346
362
|
if ((answer.mcp_calls || []).length && !answer.content) hasResult = true;
|
|
363
|
+
const toolResults = {};
|
|
347
364
|
if (answer.hasToolCalls)
|
|
348
365
|
for (const tool_call of answer.getToolCalls()) {
|
|
349
366
|
getState().log(6, "call function " + tool_call.tool_name);
|
|
@@ -395,6 +412,7 @@ const process_interaction = async (
|
|
|
395
412
|
const result = await tool.tool.process(tool_call.input, {
|
|
396
413
|
req,
|
|
397
414
|
});
|
|
415
|
+
toolResults[tool_call.tool_call_id] = result;
|
|
398
416
|
if (result.stop) stop = true;
|
|
399
417
|
if (
|
|
400
418
|
(typeof result === "object" && Object.keys(result || {}).length) ||
|
|
@@ -440,7 +458,21 @@ const process_interaction = async (
|
|
|
440
458
|
await addToContext(run, {
|
|
441
459
|
interactions: run.context.interactions,
|
|
442
460
|
});
|
|
461
|
+
|
|
462
|
+
if (myHasResult && !stop && !tool.tool.postProcess) hasResult = true;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// postprocess - now all tool calls have responses
|
|
467
|
+
if (answer.hasToolCalls)
|
|
468
|
+
for (const tool_call of answer.getToolCalls()) {
|
|
469
|
+
const tool = find_tool(tool_call.tool_name, config);
|
|
470
|
+
if (tool) {
|
|
471
|
+
let stop = false,
|
|
472
|
+
myHasResult = false;
|
|
443
473
|
if (tool.tool.postProcess && !stop) {
|
|
474
|
+
let result = toolResults[tool_call.tool_call_id];
|
|
475
|
+
|
|
444
476
|
const chat = run.context.interactions;
|
|
445
477
|
let generateUsed = false;
|
|
446
478
|
const systemPrompt = await getSystemPrompt(
|
|
@@ -461,7 +493,7 @@ const process_interaction = async (
|
|
|
461
493
|
chat,
|
|
462
494
|
appendToChat: true,
|
|
463
495
|
systemPrompt,
|
|
464
|
-
alt_config:
|
|
496
|
+
alt_config: use_alt_config,
|
|
465
497
|
...opts,
|
|
466
498
|
});
|
|
467
499
|
},
|
|
@@ -490,9 +522,17 @@ const process_interaction = async (
|
|
|
490
522
|
],
|
|
491
523
|
});
|
|
492
524
|
if (postprocres.add_response) {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
525
|
+
if (!postprocres.add_responses)
|
|
526
|
+
postprocres.add_responses = [postprocres.add_response];
|
|
527
|
+
else postprocres.add_responses.push(postprocres.add_response);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
for (const add_resp of postprocres.add_responses || []) {
|
|
531
|
+
raw_responses.push(add_resp);
|
|
532
|
+
const renderedAddResponse =
|
|
533
|
+
typeof add_resp === "string"
|
|
534
|
+
? md.render(add_resp)
|
|
535
|
+
: add_resp;
|
|
496
536
|
add_response(
|
|
497
537
|
wrapSegment(
|
|
498
538
|
wrapCard(
|
|
@@ -506,7 +546,7 @@ const process_interaction = async (
|
|
|
506
546
|
);
|
|
507
547
|
//replace tool response with this
|
|
508
548
|
// run.context.interactions.forEach((ic) => {});
|
|
509
|
-
const result =
|
|
549
|
+
const result = add_resp;
|
|
510
550
|
await sysState.functions.llm_add_message.run(
|
|
511
551
|
"assistant",
|
|
512
552
|
!result || typeof result === "string"
|
|
@@ -574,6 +614,7 @@ const process_interaction = async (
|
|
|
574
614
|
triggering_row,
|
|
575
615
|
agentsViewCfg,
|
|
576
616
|
dyn_updates,
|
|
617
|
+
is_sub_agent,
|
|
577
618
|
);
|
|
578
619
|
} else if (typeof answer === "string")
|
|
579
620
|
add_response(
|
|
@@ -594,6 +635,7 @@ const process_interaction = async (
|
|
|
594
635
|
return {
|
|
595
636
|
json: {
|
|
596
637
|
success: "ok",
|
|
638
|
+
...(is_sub_agent ? { raw_responses } : {}),
|
|
597
639
|
response: [...prevResponses, ...responses].join(""),
|
|
598
640
|
run_id: run?.id,
|
|
599
641
|
},
|
package/package.json
CHANGED
|
@@ -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;
|
package/skills/Subagent.js
CHANGED
|
@@ -66,13 +66,16 @@ class SubagentToSkill {
|
|
|
66
66
|
return div({ class: "border border-primary p-2 m-2" }, phrase);
|
|
67
67
|
},*/
|
|
68
68
|
postProcess: async ({ tool_call, req, generate, emit_update, run }) => {
|
|
69
|
-
await agent_action.run({
|
|
69
|
+
const subres = await agent_action.run({
|
|
70
70
|
row: {},
|
|
71
71
|
configuration: { ...trigger.configuration, prompt: "continue" },
|
|
72
72
|
user: req.user,
|
|
73
73
|
run_id: run.id,
|
|
74
|
+
is_sub_agent: true,
|
|
74
75
|
req,
|
|
75
76
|
});
|
|
77
|
+
if (subres.json.raw_responses)
|
|
78
|
+
return { add_responses: subres.json.raw_responses };
|
|
76
79
|
return {
|
|
77
80
|
//stop: true,
|
|
78
81
|
//add_response: result,
|
|
@@ -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;
|
package/tests/action.test.js
CHANGED
|
@@ -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. You must call both the OracleAgent and the MathsAgent tools simultaneously",
|
|
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
|
-
|
|
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 };
|