node-red-contrib-ai-agent 0.2.0 → 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/README.md +20 -3
- package/orchestrator/orchestrator.html +6 -0
- package/orchestrator/orchestrator.js +65 -19
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -93,9 +93,15 @@ Configures the AI model and API settings.
|
|
|
93
93
|
### AI Orchestrator
|
|
94
94
|
Coordinates multiple AI agents by creating and executing plans. It uses an autonomy loop (observe-think-act-reflect) to achieve complex goals.
|
|
95
95
|
|
|
96
|
+
**Key Features:**
|
|
97
|
+
- **Non-linear Planning**: Supports task dependencies (tasks wait for their predecessors).
|
|
98
|
+
- **Task Prioritization**: Executes higher priority tasks first within dependency constraints.
|
|
99
|
+
- **Dynamic Plan Revision**: Refines the plan based on task outcomes and agent feedback.
|
|
100
|
+
- **Error Recovery**: Automatically handles task failures with recovery strategies (retry, pivot, or fail).
|
|
101
|
+
|
|
96
102
|
**Properties:**
|
|
97
103
|
- **Max Iterations**: Maximum cycles for the autonomy loop
|
|
98
|
-
- **Planning Strategy**: Simple (linear) or Advanced
|
|
104
|
+
- **Planning Strategy**: Simple (linear) or Advanced (dependency-aware)
|
|
99
105
|
- **Default Goal**: Optional fallback goal
|
|
100
106
|
- **Name**: Display name for the node
|
|
101
107
|
|
|
@@ -120,10 +126,21 @@ Creates an HTTP request tool that can be used by the AI Agent to make external A
|
|
|
120
126
|
- **Headers**: JSON object of headers to include in the request
|
|
121
127
|
- **Body**: Content to send in the request body
|
|
122
128
|
|
|
129
|
+
Example: `https://api.example.com/users/${input.userId}`
|
|
130
|
+
|
|
123
131
|
**Template Variables:**
|
|
124
132
|
You can use template variables in the URL, headers, and body to reference properties from the input object that the AI provides when calling the tool.
|
|
125
133
|
|
|
126
|
-
|
|
134
|
+
### AI Tool Approval
|
|
135
|
+
Creates an approval tool that can be used by the AI Agent to request human intervention. When called, the agent's execution pauses until a human provides a response.
|
|
136
|
+
|
|
137
|
+
**Properties:**
|
|
138
|
+
- **Name**: Display name for the node
|
|
139
|
+
- **Tool Name**: Name of the tool (used by the AI to identify the tool)
|
|
140
|
+
- **Description**: Description of what the tool does (e.g., "Request approval for payments")
|
|
141
|
+
|
|
142
|
+
**Output 2 (Approval Request):** Sends a message with `msg.payload` containing the human question and `msg.approvalId` to be used for the response.
|
|
143
|
+
|
|
127
144
|
|
|
128
145
|
## Example: Basic Usage
|
|
129
146
|
|
|
@@ -179,7 +196,7 @@ The AI Orchestrator can manage complex, multi-step tasks:
|
|
|
179
196
|
4. Connect the orchestrator's second output to a **Debug** node
|
|
180
197
|
5. Configure the orchestrator with a goal (e.g., "Write a blog post and then translate it to Spanish")
|
|
181
198
|
|
|
182
|
-
The orchestrator will create a plan, dispatch the first task to the agent, reflect on the result, and then dispatch the next task until completion.
|
|
199
|
+
The orchestrator will create a plan (optionally with dependencies and priorities), dispatch the first available task to the agent, reflect on the result, and then dispatch the next task until completion. If a task fails, it can revise the plan to recover.
|
|
183
200
|
|
|
184
201
|
## Best Practices
|
|
185
202
|
|
|
@@ -72,5 +72,11 @@
|
|
|
72
72
|
|
|
73
73
|
<h3>Details</h3>
|
|
74
74
|
<p>Connect this node to one or more AI agents. The first output should flow into an agent, and the agent's output should loop back to this node's input.</p>
|
|
75
|
+
<p>The orchestrator supports advanced planning features in "Advanced" mode:</p>
|
|
76
|
+
<ul>
|
|
77
|
+
<li><strong>Dependencies:</strong> Tasks can wait for other tasks to complete (<code>dependsOn</code> array).</li>
|
|
78
|
+
<li><strong>Priorities:</strong> Tasks with higher <code>priority</code> numbers are executed first.</li>
|
|
79
|
+
<li><strong>Error Recovery:</strong> If a task fails, the orchestrator reflects on the error and can revise the plan to retry or try an alternative approach.</li>
|
|
80
|
+
</ul>
|
|
75
81
|
<p>When the goal is achieved or max iterations are reached, the final message is sent to the second output.</p>
|
|
76
82
|
</script>
|
|
@@ -48,7 +48,7 @@ module.exports = function (RED) {
|
|
|
48
48
|
// Logic based on current status
|
|
49
49
|
if (msg.orchestration.status === 'planning' || !msg.orchestration.plan) {
|
|
50
50
|
await createInitialPlan(node, msg);
|
|
51
|
-
} else {
|
|
51
|
+
} else if (msg.orchestration.currentTaskId) {
|
|
52
52
|
await reflectAndRefine(node, msg);
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -82,23 +82,33 @@ module.exports = function (RED) {
|
|
|
82
82
|
|
|
83
83
|
async function createInitialPlan(node, msg) {
|
|
84
84
|
const goal = msg.orchestration.goal;
|
|
85
|
-
const
|
|
85
|
+
const strategy = node.planningStrategy;
|
|
86
|
+
|
|
87
|
+
let prompt = `Goal: ${goal}\n\nDecompose this goal into a series of tasks for AI agents.
|
|
86
88
|
Return a JSON object with a "tasks" array. Each task should have:
|
|
87
|
-
- "id": a short string id
|
|
88
|
-
- "type": the type of task
|
|
89
|
-
- "input":
|
|
89
|
+
- "id": a short string id (e.g., "t1", "t2")
|
|
90
|
+
- "type": the type of task (e.g., "research", "code", "review")
|
|
91
|
+
- "input": detailed instruction for the agent
|
|
90
92
|
- "status": "pending"
|
|
93
|
+
- "priority": a number (1-10, default 5)
|
|
94
|
+
- "dependsOn": an array of IDs of tasks that must be completed BEFORE this task can start (empty array if none)
|
|
95
|
+
|
|
96
|
+
Note: You can use "human_approval" as a task type if you need a human to verify something before proceeding.`;
|
|
97
|
+
|
|
98
|
+
if (strategy === 'advanced') {
|
|
99
|
+
prompt += `\n\nThink about parallel execution. Group related tasks and identify bottlenecks. Ensure dependencies are logical.`;
|
|
100
|
+
}
|
|
91
101
|
|
|
92
|
-
|
|
102
|
+
prompt += `\n\nExample:
|
|
93
103
|
{
|
|
94
104
|
"tasks": [
|
|
95
|
-
{"id": "t1", "type": "research", "input": "
|
|
96
|
-
{"id": "t2", "type": "
|
|
105
|
+
{"id": "t1", "type": "research", "input": "...", "status": "pending", "priority": 10, "dependsOn": []},
|
|
106
|
+
{"id": "t2", "type": "implementation", "input": "...", "status": "pending", "priority": 5, "dependsOn": ["t1"]}
|
|
97
107
|
]
|
|
98
108
|
}`;
|
|
99
109
|
|
|
100
110
|
try {
|
|
101
|
-
const response = await callAI(msg.aiagent, prompt, "You are an AI Orchestrator that creates plans.");
|
|
111
|
+
const response = await callAI(msg.aiagent, prompt, "You are an AI Orchestrator that creates non-linear plans with dependencies.");
|
|
102
112
|
const planData = JSON.parse(extractJson(response));
|
|
103
113
|
msg.orchestration.plan = planData;
|
|
104
114
|
msg.orchestration.status = 'executing';
|
|
@@ -110,35 +120,48 @@ Example:
|
|
|
110
120
|
async function reflectAndRefine(node, msg) {
|
|
111
121
|
const currentTaskId = msg.orchestration.currentTaskId;
|
|
112
122
|
const taskResult = msg.payload;
|
|
123
|
+
const isError = msg.error ? true : false;
|
|
113
124
|
|
|
114
125
|
// Update history
|
|
115
126
|
msg.orchestration.history.push({
|
|
116
127
|
taskId: currentTaskId,
|
|
117
128
|
result: taskResult,
|
|
129
|
+
error: msg.error,
|
|
118
130
|
timestamp: new Date().toISOString()
|
|
119
131
|
});
|
|
120
132
|
|
|
121
133
|
// Update task status in plan
|
|
122
134
|
const task = msg.orchestration.plan.tasks.find(t => t.id === currentTaskId);
|
|
123
135
|
if (task) {
|
|
124
|
-
|
|
125
|
-
|
|
136
|
+
if (isError) {
|
|
137
|
+
task.status = 'failed';
|
|
138
|
+
task.error = msg.error;
|
|
139
|
+
} else {
|
|
140
|
+
task.status = 'completed';
|
|
141
|
+
task.output = taskResult;
|
|
142
|
+
}
|
|
126
143
|
}
|
|
127
144
|
|
|
128
145
|
const prompt = `Current Goal: ${msg.orchestration.goal}
|
|
129
146
|
Current Plan: ${JSON.stringify(msg.orchestration.plan)}
|
|
130
|
-
Last Task
|
|
147
|
+
Last Task ID: ${currentTaskId}
|
|
148
|
+
Last Task ${isError ? 'Error' : 'Result'}: ${JSON.stringify(isError ? msg.error : taskResult)}
|
|
149
|
+
|
|
150
|
+
Evaluate the progress.
|
|
151
|
+
1. If the last task failed, propose a recovery strategy (retry, alternative task, or fail the goal).
|
|
152
|
+
2. If the goal is achieved, set status to "completed".
|
|
153
|
+
3. If you need more information or approval from a human, add a task with type "human_approval".
|
|
154
|
+
4. Otherwise, continue execution. You may refine the plan by adding, removing, or modifying tasks.
|
|
131
155
|
|
|
132
|
-
Evaluate the progress. Should we continue with the current plan, refine it, or is the goal achieved?
|
|
133
156
|
Return a JSON object:
|
|
134
157
|
{
|
|
135
|
-
"analysis": "
|
|
158
|
+
"analysis": "detailed evaluation of progress and next steps",
|
|
136
159
|
"status": "executing" | "completed" | "failed",
|
|
137
|
-
"updatedPlan": {
|
|
160
|
+
"updatedPlan": { "tasks": [...] }
|
|
138
161
|
}`;
|
|
139
162
|
|
|
140
163
|
try {
|
|
141
|
-
const response = await callAI(msg.aiagent, prompt, "You are an AI Orchestrator that reflects on progress.");
|
|
164
|
+
const response = await callAI(msg.aiagent, prompt, "You are an AI Orchestrator that reflects on progress and manages plan revisions.");
|
|
142
165
|
const reflection = JSON.parse(extractJson(response));
|
|
143
166
|
|
|
144
167
|
msg.orchestration.status = reflection.status;
|
|
@@ -146,14 +169,37 @@ Return a JSON object:
|
|
|
146
169
|
msg.orchestration.plan = reflection.updatedPlan;
|
|
147
170
|
}
|
|
148
171
|
} catch (error) {
|
|
149
|
-
node.warn(`Reflection failed, continuing with current plan: ${error.message}`);
|
|
150
|
-
// Fallback:
|
|
172
|
+
node.warn(`Reflection failed, continuing with current plan state: ${error.message}`);
|
|
173
|
+
// Fallback: stay in executing status if it was executing, let getNextTask decide
|
|
151
174
|
}
|
|
152
175
|
}
|
|
153
176
|
|
|
154
177
|
function getNextTask(plan) {
|
|
155
178
|
if (!plan || !plan.tasks) return null;
|
|
156
|
-
|
|
179
|
+
|
|
180
|
+
// Find tasks that are pending AND all their dependencies are completed
|
|
181
|
+
const eligibleTasks = plan.tasks.filter(t => {
|
|
182
|
+
if (t.status !== 'pending') return false;
|
|
183
|
+
|
|
184
|
+
if (!t.dependsOn || t.dependsOn.length === 0) return true;
|
|
185
|
+
|
|
186
|
+
return t.dependsOn.every(depId => {
|
|
187
|
+
const depTask = plan.tasks.find(pt => pt.id === depId);
|
|
188
|
+
return depTask && depTask.status === 'completed';
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (eligibleTasks.length === 0) return null;
|
|
193
|
+
|
|
194
|
+
// Sort by priority (descending) then by ID
|
|
195
|
+
eligibleTasks.sort((a, b) => {
|
|
196
|
+
const priorityA = a.priority || 5;
|
|
197
|
+
const priorityB = b.priority || 5;
|
|
198
|
+
if (priorityB !== priorityA) return priorityB - priorityA;
|
|
199
|
+
return a.id.localeCompare(b.id);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return eligibleTasks[0];
|
|
157
203
|
}
|
|
158
204
|
|
|
159
205
|
async function callAI(aiConfig, prompt, systemPrompt) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-ai-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "AI Agent for Node-RED",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -62,7 +62,8 @@
|
|
|
62
62
|
"ai-tool-http": "./tool-http/ai-tool-http.js",
|
|
63
63
|
"ai-memory-file": "./memory-file/memory-file.js",
|
|
64
64
|
"ai-memory-inmem": "./memory-inmem/memory-inmem.js",
|
|
65
|
-
"ai-orchestrator": "./orchestrator/orchestrator.js"
|
|
65
|
+
"ai-orchestrator": "./orchestrator/orchestrator.js",
|
|
66
|
+
"ai-tool-approval": "./tool-approval/ai-tool-approval.js"
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
}
|