@saltcorn/agents 0.6.12 → 0.7.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/common.js +23 -4
- package/package.json +2 -2
- package/skills/Fetch.js +3 -0
- package/skills/GenerateAndRunJsCode.js +25 -3
- package/skills/Subagent.js +94 -0
- package/tests/action.test.js +36 -2
- package/tests/agentcfg.js +23 -1
- package/tests/configs.js +9 -0
- package/tests/view.test.js +6 -6
package/common.js
CHANGED
|
@@ -37,6 +37,7 @@ const get_skills = () => {
|
|
|
37
37
|
require("./skills/RunJsCode"),
|
|
38
38
|
require("./skills/GenerateAndRunJsCode"),
|
|
39
39
|
require("./skills/Fetch"),
|
|
40
|
+
require("./skills/Subagent"),
|
|
40
41
|
//require("./skills/AdaptiveFeedback"),
|
|
41
42
|
...exchange_skills,
|
|
42
43
|
];
|
|
@@ -250,7 +251,14 @@ const process_interaction = async (
|
|
|
250
251
|
}
|
|
251
252
|
};
|
|
252
253
|
}
|
|
253
|
-
|
|
254
|
+
|
|
255
|
+
const lastInteract =
|
|
256
|
+
run.context.interactions[run.context.interactions.length - 1];
|
|
257
|
+
|
|
258
|
+
const answer = await sysState.functions.llm_generate.run(
|
|
259
|
+
lastInteract?.role === "user" ? "" : "Continue",
|
|
260
|
+
complArgs,
|
|
261
|
+
);
|
|
254
262
|
|
|
255
263
|
//console.log("answer", answer);
|
|
256
264
|
|
|
@@ -330,7 +338,7 @@ const process_interaction = async (
|
|
|
330
338
|
if ((answer.mcp_calls || []).length && !answer.content) hasResult = true;
|
|
331
339
|
if (answer.hasToolCalls)
|
|
332
340
|
for (const tool_call of answer.getToolCalls()) {
|
|
333
|
-
|
|
341
|
+
getState().log(6, "call function " + tool_call.tool_name);
|
|
334
342
|
|
|
335
343
|
await addToContext(run, {
|
|
336
344
|
funcalls: {
|
|
@@ -434,6 +442,7 @@ const process_interaction = async (
|
|
|
434
442
|
result,
|
|
435
443
|
chat,
|
|
436
444
|
req,
|
|
445
|
+
run,
|
|
437
446
|
async generate(prompt, opts = {}) {
|
|
438
447
|
generateUsed = true;
|
|
439
448
|
return await sysState.functions.llm_generate.run(prompt, {
|
|
@@ -481,7 +490,7 @@ const process_interaction = async (
|
|
|
481
490
|
// run.context.interactions.forEach((ic) => {});
|
|
482
491
|
const result = postprocres.add_response;
|
|
483
492
|
await sysState.functions.llm_add_message.run(
|
|
484
|
-
"
|
|
493
|
+
"assistant",
|
|
485
494
|
!result || typeof result === "string"
|
|
486
495
|
? {
|
|
487
496
|
type: "text",
|
|
@@ -493,9 +502,19 @@ const process_interaction = async (
|
|
|
493
502
|
},
|
|
494
503
|
{
|
|
495
504
|
chat: run.context.interactions,
|
|
496
|
-
tool_call,
|
|
497
505
|
},
|
|
498
506
|
);
|
|
507
|
+
if (!postprocres.stop)
|
|
508
|
+
await sysState.functions.llm_add_message.run(
|
|
509
|
+
"user",
|
|
510
|
+
{
|
|
511
|
+
type: "text",
|
|
512
|
+
value: "Continue",
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
chat: run.context.interactions,
|
|
516
|
+
},
|
|
517
|
+
);
|
|
499
518
|
}
|
|
500
519
|
if (postprocres.add_user_action && viewname) {
|
|
501
520
|
const user_actions = Array.isArray()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "AI agents for Saltcorn",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"jest": "^29.7.0"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
|
-
"test": "jest tests --runInBand"
|
|
22
|
+
"test": "jest tests --runInBand --verbose"
|
|
23
23
|
},
|
|
24
24
|
"eslintConfig": {
|
|
25
25
|
"extends": "eslint:recommended",
|
package/skills/Fetch.js
CHANGED
|
@@ -33,6 +33,9 @@ class FetchSkill {
|
|
|
33
33
|
static async configFields() {
|
|
34
34
|
return [];
|
|
35
35
|
}
|
|
36
|
+
systemPrompt() {
|
|
37
|
+
return "If you need to retrieve the contents of a web page, use the fetch_web_page to make a GET request to a specified URL.";
|
|
38
|
+
}
|
|
36
39
|
|
|
37
40
|
provideTools = () => {
|
|
38
41
|
return {
|
|
@@ -142,7 +142,29 @@ ${this.allow_fetch ? "\nYou can use the standard fetch JavaScript function to ma
|
|
|
142
142
|
${this.allow_table ? getTablePrompt(this.read_only) : ""}
|
|
143
143
|
|
|
144
144
|
The code you write can use await at the top level, and should return
|
|
145
|
-
(at the top level) a string (which can contain HTML tags) with the response which will be shown
|
|
145
|
+
(at the top level) either a string (which can contain HTML tags) with the response which will be shown
|
|
146
|
+
to the user, or a JSON object which will then be further summarized for the user.
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
|
|
150
|
+
\`\`\`javascript
|
|
151
|
+
|
|
152
|
+
const x = await myAsyncFunction()
|
|
153
|
+
const y = await anotherAsyncFunction(x)
|
|
154
|
+
|
|
155
|
+
return \`The eggs are \${x} and the why is \${y}\`
|
|
156
|
+
\`\`\`
|
|
157
|
+
|
|
158
|
+
or
|
|
159
|
+
|
|
160
|
+
\`\`\`javascript
|
|
161
|
+
|
|
162
|
+
const x = await myAsyncFunction()
|
|
163
|
+
const y = await anotherAsyncFunction(x)
|
|
164
|
+
|
|
165
|
+
return { x, y }
|
|
166
|
+
\`\`\`
|
|
167
|
+
|
|
146
168
|
|
|
147
169
|
Now generate the JavaScript code required by the user.`,
|
|
148
170
|
);
|
|
@@ -156,9 +178,9 @@ Now generate the JavaScript code required by the user.`,
|
|
|
156
178
|
emit_update("Running code");
|
|
157
179
|
const res = await this.runCode(js_code, { user: req.user });
|
|
158
180
|
//console.log("code response", res);
|
|
159
|
-
getState().log(6, "Code answer: " + JSON.stringify(res));
|
|
181
|
+
getState().log(6, "Code answer: " + JSON.stringify(res));
|
|
160
182
|
return {
|
|
161
|
-
|
|
183
|
+
stop: typeof res ==="string",
|
|
162
184
|
add_response: res,
|
|
163
185
|
};
|
|
164
186
|
},
|
|
@@ -0,0 +1,94 @@
|
|
|
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 View = require("@saltcorn/data/models/view");
|
|
6
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
7
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
8
|
+
const db = require("@saltcorn/data/db");
|
|
9
|
+
const { fieldProperties } = require("./helpers");
|
|
10
|
+
const agent_action = require("../action");
|
|
11
|
+
|
|
12
|
+
class SubagentToSkill {
|
|
13
|
+
static skill_name = "Subagent";
|
|
14
|
+
|
|
15
|
+
get skill_label() {
|
|
16
|
+
return this.agent_name;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
constructor(cfg) {
|
|
20
|
+
Object.assign(this, cfg);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
systemPrompt() {
|
|
24
|
+
const trigger = Trigger.findOne({ name: this.agent_name });
|
|
25
|
+
if (trigger.description)
|
|
26
|
+
return `${this.agent_name} tool: ${trigger.description}`;
|
|
27
|
+
else return "";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static async configFields() {
|
|
31
|
+
const actions = await Trigger.find({ action: "Agent" });
|
|
32
|
+
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
name: "agent_name",
|
|
36
|
+
label: "Agent",
|
|
37
|
+
sublabel: a(
|
|
38
|
+
{
|
|
39
|
+
"data-dyn-href": `\`/actions/configure/\${agent_name}\``,
|
|
40
|
+
target: "_blank",
|
|
41
|
+
},
|
|
42
|
+
"Configure",
|
|
43
|
+
),
|
|
44
|
+
type: "String",
|
|
45
|
+
required: true,
|
|
46
|
+
attributes: { options: actions.map((a) => a.name) },
|
|
47
|
+
},
|
|
48
|
+
// TODO: confirm, show response, show argument
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
provideTools = () => {
|
|
53
|
+
let properties = {};
|
|
54
|
+
|
|
55
|
+
const trigger = Trigger.findOne({ name: this.agent_name });
|
|
56
|
+
if (!trigger)
|
|
57
|
+
throw new Error(`Trigger skill: cannot find trigger ${this.agent_name}`);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
type: "function",
|
|
61
|
+
process: async (row, { req }) => {
|
|
62
|
+
//const result = await trigger.runWithoutRow({ user: req?.user, row });
|
|
63
|
+
return "Workflow started";
|
|
64
|
+
},
|
|
65
|
+
/*renderToolCall({ phrase }, { req }) {
|
|
66
|
+
return div({ class: "border border-primary p-2 m-2" }, phrase);
|
|
67
|
+
},*/
|
|
68
|
+
postProcess: async ({ tool_call, req, generate, emit_update, run }) => {
|
|
69
|
+
await agent_action.run({
|
|
70
|
+
row: {},
|
|
71
|
+
configuration: { ...trigger.configuration, prompt: "continue" },
|
|
72
|
+
user: req.user,
|
|
73
|
+
run_id: run.id,
|
|
74
|
+
req,
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
//stop: true,
|
|
78
|
+
//add_response: result,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
function: {
|
|
82
|
+
name: trigger.name,
|
|
83
|
+
description: trigger.description,
|
|
84
|
+
parameters: {
|
|
85
|
+
type: "object",
|
|
86
|
+
//required: ["action_javascript_code", "action_name"],
|
|
87
|
+
properties,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = SubagentToSkill;
|
package/tests/action.test.js
CHANGED
|
@@ -3,6 +3,7 @@ const View = require("@saltcorn/data/models/view");
|
|
|
3
3
|
const WorkflowRuns = require("@saltcorn/data/models/workflow_run");
|
|
4
4
|
const Table = require("@saltcorn/data/models/table");
|
|
5
5
|
const Plugin = require("@saltcorn/data/models/plugin");
|
|
6
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
6
7
|
|
|
7
8
|
const { mockReqRes } = require("@saltcorn/data/tests/mocks");
|
|
8
9
|
const { afterAll, beforeAll, describe, it, expect } = require("@jest/globals");
|
|
@@ -20,6 +21,17 @@ beforeAll(async () => {
|
|
|
20
21
|
await require("@saltcorn/data/db/fixtures")();
|
|
21
22
|
|
|
22
23
|
getState().registerPlugin("base", require("@saltcorn/data/base-plugin"));
|
|
24
|
+
|
|
25
|
+
await Trigger.create({
|
|
26
|
+
name: "MathsAgent",
|
|
27
|
+
description: "Answer questions about arithmetic",
|
|
28
|
+
action: "Agent",
|
|
29
|
+
when_trigger: "Never",
|
|
30
|
+
configuration: require("./agentcfg").maths_agent_cfg,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
await getState().refresh_triggers(false);
|
|
34
|
+
//await getState().setConfig("log_level", 6);
|
|
23
35
|
});
|
|
24
36
|
|
|
25
37
|
jest.setTimeout(30000);
|
|
@@ -52,7 +64,7 @@ for (const nameconfig of require("./configs")) {
|
|
|
52
64
|
it("generates text", async () => {
|
|
53
65
|
const result = await action.run({
|
|
54
66
|
row: { theprompt: "What is the word of the day?" },
|
|
55
|
-
configuration: require("./agentcfg"),
|
|
67
|
+
configuration: require("./agentcfg").agent1,
|
|
56
68
|
user,
|
|
57
69
|
req: { user },
|
|
58
70
|
});
|
|
@@ -67,7 +79,7 @@ for (const nameconfig of require("./configs")) {
|
|
|
67
79
|
theprompt:
|
|
68
80
|
"How many pages are there in the book by Herman Melville in the database?",
|
|
69
81
|
},
|
|
70
|
-
configuration: require("./agentcfg"),
|
|
82
|
+
configuration: require("./agentcfg").agent1,
|
|
71
83
|
user,
|
|
72
84
|
run_id: run.id,
|
|
73
85
|
req: { ...mockReqRes.req, user },
|
|
@@ -75,6 +87,28 @@ for (const nameconfig of require("./configs")) {
|
|
|
75
87
|
expect(result.json.response).toContain("967");
|
|
76
88
|
//const run1 = await WorkflowRuns.findOne({});
|
|
77
89
|
});
|
|
90
|
+
it("generates and runs js code", async () => {
|
|
91
|
+
const result = await action.run({
|
|
92
|
+
row: {
|
|
93
|
+
theprompt: "What is the 16th Fibonacci number (when F1=1 and F2=1) ?",
|
|
94
|
+
},
|
|
95
|
+
configuration: require("./agentcfg").maths_agent_cfg,
|
|
96
|
+
user,
|
|
97
|
+
req: { user },
|
|
98
|
+
});
|
|
99
|
+
expect(result.json.response).toContain("987");
|
|
100
|
+
});
|
|
101
|
+
it("run subagent", async () => {
|
|
102
|
+
const result = await action.run({
|
|
103
|
+
row: {
|
|
104
|
+
theprompt: "What is the 16th Fibonacci number (when F1=1 and F2=1) ?",
|
|
105
|
+
},
|
|
106
|
+
configuration: require("./agentcfg").agent1,
|
|
107
|
+
user,
|
|
108
|
+
req: { user },
|
|
109
|
+
});
|
|
110
|
+
expect(result.json.response).toContain("987");
|
|
111
|
+
});
|
|
78
112
|
});
|
|
79
113
|
//break;
|
|
80
114
|
}
|
package/tests/agentcfg.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
const agent1 = {
|
|
2
2
|
model: "",
|
|
3
3
|
prompt: "{{theprompt}}",
|
|
4
4
|
skills: [
|
|
@@ -41,6 +41,28 @@ module.exports = {
|
|
|
41
41
|
add_sys_prompt:
|
|
42
42
|
"Use this tool to search information about books in a book database. Each book is indexed by author and has page counts. If the user asks for information about books by a specific author, use this tool.",
|
|
43
43
|
},
|
|
44
|
+
{
|
|
45
|
+
agent_name: "MathsAgent",
|
|
46
|
+
skill_type: "Subagent",
|
|
47
|
+
},
|
|
44
48
|
],
|
|
45
49
|
sys_prompt: "",
|
|
46
50
|
};
|
|
51
|
+
|
|
52
|
+
const maths_agent_cfg = {
|
|
53
|
+
model: "",
|
|
54
|
+
prompt: "{{theprompt}}",
|
|
55
|
+
skills: [
|
|
56
|
+
{
|
|
57
|
+
tool_name: "generate_arithmetic_code",
|
|
58
|
+
skill_type: "Generate and run JavaScript code",
|
|
59
|
+
add_sys_prompt: "",
|
|
60
|
+
code_description: "",
|
|
61
|
+
tool_description: "Generate Javascript code to solve arithmetic problems",
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
sys_prompt:
|
|
65
|
+
"If the user asks an arithmetic question, generate javascript code to solve it with the generate_arithmetic_code tool",
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
module.exports = { agent1, maths_agent_cfg };
|
package/tests/configs.js
CHANGED
|
@@ -31,4 +31,13 @@ module.exports = [
|
|
|
31
31
|
temperature: 0.7,
|
|
32
32
|
ai_sdk_provider: "OpenAI",
|
|
33
33
|
},
|
|
34
|
+
{
|
|
35
|
+
name: "AI SDK Anthropic",
|
|
36
|
+
model: "claude-sonnet-4-6",
|
|
37
|
+
api_key: process.env.ANTHROPIC_API_KEY,
|
|
38
|
+
backend: "AI SDK",
|
|
39
|
+
image_model: "gpt-image-1",
|
|
40
|
+
temperature: 0.7,
|
|
41
|
+
ai_sdk_provider: "Anthropic",
|
|
42
|
+
},
|
|
34
43
|
];
|
package/tests/view.test.js
CHANGED
|
@@ -42,10 +42,10 @@ for (const nameconfig of require("./configs")) {
|
|
|
42
42
|
description: "",
|
|
43
43
|
action: "Agent",
|
|
44
44
|
when_trigger: "Never",
|
|
45
|
-
configuration: require("./agentcfg"),
|
|
45
|
+
configuration: require("./agentcfg").agent1,
|
|
46
46
|
});
|
|
47
|
-
|
|
48
|
-
await getState().refresh_triggers(false)
|
|
47
|
+
|
|
48
|
+
await getState().refresh_triggers(false);
|
|
49
49
|
const view = await View.create({
|
|
50
50
|
name: "AgentView",
|
|
51
51
|
description: "",
|
|
@@ -79,10 +79,10 @@ for (const nameconfig of require("./configs")) {
|
|
|
79
79
|
default_render_page: "",
|
|
80
80
|
exttable_name: null,
|
|
81
81
|
});
|
|
82
|
-
await getState().refresh_views(false)
|
|
82
|
+
await getState().refresh_views(false);
|
|
83
83
|
|
|
84
|
-
const result = await view.run({}, mockReqRes);
|
|
85
|
-
expect(result).toContain(">Pirate<")
|
|
84
|
+
const result = await view.run({}, mockReqRes);
|
|
85
|
+
expect(result).toContain(">Pirate<");
|
|
86
86
|
});
|
|
87
87
|
});
|
|
88
88
|
break; //only need to test one config iteration
|