@saltcorn/agents 0.6.11 → 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 +52 -9
- package/package.json +2 -2
- package/skills/Fetch.js +3 -0
- package/skills/GenerateAndRunJsCode.js +29 -4
- 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
|
];
|
|
@@ -95,6 +96,24 @@ const get_initial_interactions = async (config, user, triggering_row) => {
|
|
|
95
96
|
return interacts;
|
|
96
97
|
};
|
|
97
98
|
|
|
99
|
+
const getSystemPrompt = async (config, user, triggering_row, formbody) => {
|
|
100
|
+
let sysPrompts = [
|
|
101
|
+
interpolate(config.sys_prompt, triggering_row || {}, user, "System prompt"),
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
const skills = get_skill_instances(config);
|
|
105
|
+
for (const skill of skills) {
|
|
106
|
+
const sysPr = await skill.systemPrompt?.({
|
|
107
|
+
...(formbody || {}),
|
|
108
|
+
user,
|
|
109
|
+
triggering_row,
|
|
110
|
+
});
|
|
111
|
+
if (sysPr) sysPrompts.push(sysPr);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return sysPrompts.join("\n\n");
|
|
115
|
+
};
|
|
116
|
+
|
|
98
117
|
const getCompletionArguments = async (
|
|
99
118
|
config,
|
|
100
119
|
user,
|
|
@@ -232,7 +251,14 @@ const process_interaction = async (
|
|
|
232
251
|
}
|
|
233
252
|
};
|
|
234
253
|
}
|
|
235
|
-
|
|
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
|
+
);
|
|
236
262
|
|
|
237
263
|
//console.log("answer", answer);
|
|
238
264
|
|
|
@@ -312,7 +338,7 @@ const process_interaction = async (
|
|
|
312
338
|
if ((answer.mcp_calls || []).length && !answer.content) hasResult = true;
|
|
313
339
|
if (answer.hasToolCalls)
|
|
314
340
|
for (const tool_call of answer.getToolCalls()) {
|
|
315
|
-
|
|
341
|
+
getState().log(6, "call function " + tool_call.tool_name);
|
|
316
342
|
|
|
317
343
|
await addToContext(run, {
|
|
318
344
|
funcalls: {
|
|
@@ -326,10 +352,9 @@ const process_interaction = async (
|
|
|
326
352
|
let stop = false,
|
|
327
353
|
myHasResult = false;
|
|
328
354
|
if (stream && viewname) {
|
|
329
|
-
let content =
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
);
|
|
355
|
+
let content =
|
|
356
|
+
(tool.skill.skill_label || tool.skill.constructor.skill_name) +
|
|
357
|
+
" ";
|
|
333
358
|
const view = View.findOne({ name: viewname });
|
|
334
359
|
const pageLoadTag = req.body.page_load_tag;
|
|
335
360
|
view.emitRealTimeEvent(
|
|
@@ -406,16 +431,24 @@ const process_interaction = async (
|
|
|
406
431
|
if (tool.tool.postProcess && !stop) {
|
|
407
432
|
const chat = run.context.interactions;
|
|
408
433
|
let generateUsed = false;
|
|
434
|
+
const systemPrompt = await getSystemPrompt(
|
|
435
|
+
config,
|
|
436
|
+
req.user,
|
|
437
|
+
triggering_row,
|
|
438
|
+
req.body,
|
|
439
|
+
);
|
|
409
440
|
const postprocres = await tool.tool.postProcess({
|
|
410
441
|
tool_call,
|
|
411
442
|
result,
|
|
412
443
|
chat,
|
|
413
444
|
req,
|
|
445
|
+
run,
|
|
414
446
|
async generate(prompt, opts = {}) {
|
|
415
447
|
generateUsed = true;
|
|
416
448
|
return await sysState.functions.llm_generate.run(prompt, {
|
|
417
449
|
chat,
|
|
418
450
|
appendToChat: true,
|
|
451
|
+
systemPrompt,
|
|
419
452
|
...opts,
|
|
420
453
|
});
|
|
421
454
|
},
|
|
@@ -426,7 +459,7 @@ const process_interaction = async (
|
|
|
426
459
|
view.emitRealTimeEvent(
|
|
427
460
|
`STREAM_CHUNK?page_load_tag=${pageLoadTag}`,
|
|
428
461
|
{
|
|
429
|
-
content:
|
|
462
|
+
content: s + " ",
|
|
430
463
|
},
|
|
431
464
|
);
|
|
432
465
|
},
|
|
@@ -457,7 +490,7 @@ const process_interaction = async (
|
|
|
457
490
|
// run.context.interactions.forEach((ic) => {});
|
|
458
491
|
const result = postprocres.add_response;
|
|
459
492
|
await sysState.functions.llm_add_message.run(
|
|
460
|
-
"
|
|
493
|
+
"assistant",
|
|
461
494
|
!result || typeof result === "string"
|
|
462
495
|
? {
|
|
463
496
|
type: "text",
|
|
@@ -469,9 +502,19 @@ const process_interaction = async (
|
|
|
469
502
|
},
|
|
470
503
|
{
|
|
471
504
|
chat: run.context.interactions,
|
|
472
|
-
tool_call,
|
|
473
505
|
},
|
|
474
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
|
+
);
|
|
475
518
|
}
|
|
476
519
|
if (postprocres.add_user_action && viewname) {
|
|
477
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,20 +142,45 @@ ${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
|
);
|
|
149
|
-
getState().log(
|
|
171
|
+
getState().log(
|
|
172
|
+
6,
|
|
173
|
+
"Generated code:\n--BEGIN CODE--\n" + str + "\n--END CODE--\n",
|
|
174
|
+
);
|
|
150
175
|
const js_code = str.includes("```javascript")
|
|
151
176
|
? str.split("```javascript")[1].split("```")[0]
|
|
152
177
|
: str;
|
|
153
178
|
emit_update("Running code");
|
|
154
179
|
const res = await this.runCode(js_code, { user: req.user });
|
|
155
180
|
//console.log("code response", res);
|
|
156
|
-
getState().log(6, "Code answer: " + JSON.stringify(res));
|
|
181
|
+
getState().log(6, "Code answer: " + JSON.stringify(res));
|
|
157
182
|
return {
|
|
158
|
-
|
|
183
|
+
stop: typeof res ==="string",
|
|
159
184
|
add_response: res,
|
|
160
185
|
};
|
|
161
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
|