node-red-contrib-ai-agent 0.0.7 → 0.2.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 CHANGED
@@ -23,6 +23,7 @@ Your feedback and contributions are highly appreciated!
23
23
  - **In-Memory**: Store conversation context in memory (volatile)
24
24
  - **File-based**: Persist conversation context to disk
25
25
  - **AI Model Node**: Configure AI models and API settings
26
+ - **AI Orchestrator Node**: Coordinate multiple agents and create autonomous plans
26
27
  - **Tool Integration**: Extend functionality with custom tools
27
28
  - **Stateless Design**: Memory nodes are stateless, making them more reliable and scalable
28
29
  - **Context Management**: Automatic conversation history management with configurable retention
@@ -89,6 +90,15 @@ Configures the AI model and API settings.
89
90
  - **API Key**: Your OpenRouter API key
90
91
  - **Name**: Display name for the node
91
92
 
93
+ ### AI Orchestrator
94
+ Coordinates multiple AI agents by creating and executing plans. It uses an autonomy loop (observe-think-act-reflect) to achieve complex goals.
95
+
96
+ **Properties:**
97
+ - **Max Iterations**: Maximum cycles for the autonomy loop
98
+ - **Planning Strategy**: Simple (linear) or Advanced
99
+ - **Default Goal**: Optional fallback goal
100
+ - **Name**: Display name for the node
101
+
92
102
  ### AI Tool Function
93
103
  Creates a JavaScript function tool that can be used by the AI Agent.
94
104
 
@@ -159,6 +169,18 @@ For more complex scenarios, you can chain multiple agents to process messages in
159
169
 
160
170
  Each agent will maintain its own conversation context based on its memory configuration.
161
171
 
172
+ ## Example: Autonomous Orchestration
173
+
174
+ The AI Orchestrator can manage complex, multi-step tasks:
175
+
176
+ 1. Add an **AI Orchestrator** node
177
+ 2. Connect its first output to an **AI Agent**
178
+ 3. Connect the agent's output back to the **AI Orchestrator** input
179
+ 4. Connect the orchestrator's second output to a **Debug** node
180
+ 5. Configure the orchestrator with a goal (e.g., "Write a blog post and then translate it to Spanish")
181
+
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.
183
+
162
184
  ## Best Practices
163
185
 
164
186
  ### Memory Management
@@ -0,0 +1,76 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('ai-orchestrator', {
3
+ category: 'ai agent',
4
+ color: '#E2D96E',
5
+ defaults: {
6
+ name: { value: "" },
7
+ maxIterations: { value: 5, validate: RED.validators.number() },
8
+ planningStrategy: { value: "simple" },
9
+ defaultGoal: { value: "" }
10
+ },
11
+ inputs: 1,
12
+ outputs: 2,
13
+ outputLabels: ["Task Dispatch", "Final Result"],
14
+ icon: "font-awesome/fa-sitemap",
15
+ label: function () {
16
+ return this.name || "ai orchestrator";
17
+ },
18
+ paletteLabel: "ai orchestrator"
19
+ });
20
+ </script>
21
+
22
+ <script type="text/html" data-template-name="ai-orchestrator">
23
+ <div class="form-row">
24
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
25
+ <input type="text" id="node-input-name" placeholder="Name">
26
+ </div>
27
+ <div class="form-row">
28
+ <label for="node-input-maxIterations"><i class="fa fa-repeat"></i> Max Iterations</label>
29
+ <input type="number" id="node-input-maxIterations" placeholder="5">
30
+ </div>
31
+ <div class="form-row">
32
+ <label for="node-input-planningStrategy"><i class="fa fa-cog"></i> Planning Strategy</label>
33
+ <select id="node-input-planningStrategy">
34
+ <option value="simple">Simple (Linear)</option>
35
+ <option value="advanced">Advanced (LLM-optimized)</option>
36
+ </select>
37
+ </div>
38
+ <div class="form-row">
39
+ <label for="node-input-defaultGoal"><i class="fa fa-bullseye"></i> Default Goal</label>
40
+ <textarea id="node-input-defaultGoal" style="width: 100%" rows="3" placeholder="Optional default goal for the orchestrator"></textarea>
41
+ </div>
42
+ </script>
43
+
44
+ <script type="text/html" data-content-help-name="ai-orchestrator">
45
+ <p>The AI Orchestrator node manages complex tasks by decomposing them into a plan and dispatching individual tasks to other AI agents.</p>
46
+
47
+ <h3>Inputs</h3>
48
+ <dl class="message-properties">
49
+ <dt>payload <span class="property-type">string | object</span></dt>
50
+ <dd>The goal or task description if starting a new plan.</dd>
51
+ <dt>orchestration <span class="property-type">object</span></dt>
52
+ <dd>The internal state of the orchestration loop (passed back from agents).</dd>
53
+ <dt>aiagent <span class="property-type">object</span></dt>
54
+ <dd>The AI model configuration (required for planning and reflection).</dd>
55
+ </dl>
56
+
57
+ <h3>Outputs</h3>
58
+ <ol class="node-ports">
59
+ <li>Task Dispatch
60
+ <dl class="message-properties">
61
+ <dt>msg.payload <span class="property-type">string</span></dt>
62
+ <dd>The instruction for the next task.</dd>
63
+ </dl>
64
+ </li>
65
+ <li>Final Result
66
+ <dl class="message-properties">
67
+ <dt>msg.orchestration <span class="property-type">object</span></dt>
68
+ <dd>The full orchestration history and final result status.</dd>
69
+ </dl>
70
+ </li>
71
+ </ol>
72
+
73
+ <h3>Details</h3>
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>When the goal is achieved or max iterations are reached, the final message is sent to the second output.</p>
76
+ </script>
@@ -0,0 +1,186 @@
1
+ const axios = require('axios');
2
+
3
+ module.exports = function (RED) {
4
+ function AiOrchestratorNode(config) {
5
+ RED.nodes.createNode(this, config);
6
+ const node = this;
7
+
8
+ this.name = config.name || 'AI Orchestrator';
9
+ this.maxIterations = parseInt(config.maxIterations) || 5;
10
+ this.planningStrategy = config.planningStrategy || 'simple';
11
+ this.defaultGoal = config.defaultGoal || '';
12
+
13
+ node.on('input', async function (msg, send, done) {
14
+ send = send || function () { node.send.apply(node, arguments) };
15
+ node.status({ fill: 'blue', shape: 'dot', text: 'thinking...' });
16
+
17
+ try {
18
+ // Initialize orchestration state if not present
19
+ if (!msg.orchestration) {
20
+ msg.orchestration = {
21
+ planId: 'plan-' + Date.now(),
22
+ iterations: 0,
23
+ goal: msg.payload || node.defaultGoal,
24
+ status: 'planning',
25
+ history: [],
26
+ plan: null
27
+ };
28
+ } else {
29
+ msg.orchestration.iterations++;
30
+ }
31
+
32
+ // Check for max iterations
33
+ if (msg.orchestration.iterations >= node.maxIterations) {
34
+ node.warn('Max iterations reached');
35
+ msg.orchestration.status = 'failed';
36
+ msg.orchestration.error = 'Max iterations reached';
37
+ node.status({ fill: 'red', shape: 'dot', text: 'max iterations' });
38
+ send([null, msg]); // Output 2 for final result
39
+ if (done) done();
40
+ return;
41
+ }
42
+
43
+ // AI Configuration check
44
+ if (!msg.aiagent || !msg.aiagent.apiKey) {
45
+ throw new Error('AI Model configuration missing or API key not found.');
46
+ }
47
+
48
+ // Logic based on current status
49
+ if (msg.orchestration.status === 'planning' || !msg.orchestration.plan) {
50
+ await createInitialPlan(node, msg);
51
+ } else {
52
+ await reflectAndRefine(node, msg);
53
+ }
54
+
55
+ // Dispatch or Finalize
56
+ if (msg.orchestration.status === 'completed' || msg.orchestration.status === 'failed') {
57
+ node.status({ fill: 'green', shape: 'dot', text: msg.orchestration.status });
58
+ send([null, msg]); // Output 2
59
+ } else {
60
+ const nextTask = getNextTask(msg.orchestration.plan);
61
+ if (nextTask) {
62
+ msg.payload = nextTask.input;
63
+ msg.topic = nextTask.type;
64
+ msg.orchestration.currentTaskId = nextTask.id;
65
+ node.status({ fill: 'blue', shape: 'ring', text: `dispatching: ${nextTask.id}` });
66
+ send([msg, null]); // Output 1
67
+ } else {
68
+ msg.orchestration.status = 'completed';
69
+ node.status({ fill: 'green', shape: 'dot', text: 'completed' });
70
+ send([null, msg]); // Output 2
71
+ }
72
+ }
73
+
74
+ if (done) done();
75
+ } catch (error) {
76
+ node.status({ fill: 'red', shape: 'ring', text: 'error' });
77
+ node.error(error.message, msg);
78
+ if (done) done(error);
79
+ }
80
+ });
81
+ }
82
+
83
+ async function createInitialPlan(node, msg) {
84
+ const goal = msg.orchestration.goal;
85
+ const prompt = `Goal: ${goal}\n\nDecompose this goal into a series of tasks for AI agents.
86
+ 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": what the agent should do
90
+ - "status": "pending"
91
+
92
+ Example:
93
+ {
94
+ "tasks": [
95
+ {"id": "t1", "type": "research", "input": "Find information about X", "status": "pending"},
96
+ {"id": "t2", "type": "summary", "input": "Summarize the findings", "status": "pending"}
97
+ ]
98
+ }`;
99
+
100
+ try {
101
+ const response = await callAI(msg.aiagent, prompt, "You are an AI Orchestrator that creates plans.");
102
+ const planData = JSON.parse(extractJson(response));
103
+ msg.orchestration.plan = planData;
104
+ msg.orchestration.status = 'executing';
105
+ } catch (error) {
106
+ throw new Error(`Planning failed: ${error.message}`);
107
+ }
108
+ }
109
+
110
+ async function reflectAndRefine(node, msg) {
111
+ const currentTaskId = msg.orchestration.currentTaskId;
112
+ const taskResult = msg.payload;
113
+
114
+ // Update history
115
+ msg.orchestration.history.push({
116
+ taskId: currentTaskId,
117
+ result: taskResult,
118
+ timestamp: new Date().toISOString()
119
+ });
120
+
121
+ // Update task status in plan
122
+ const task = msg.orchestration.plan.tasks.find(t => t.id === currentTaskId);
123
+ if (task) {
124
+ task.status = 'completed';
125
+ task.output = taskResult;
126
+ }
127
+
128
+ const prompt = `Current Goal: ${msg.orchestration.goal}
129
+ Current Plan: ${JSON.stringify(msg.orchestration.plan)}
130
+ Last Task Result: ${JSON.stringify(taskResult)}
131
+
132
+ Evaluate the progress. Should we continue with the current plan, refine it, or is the goal achieved?
133
+ Return a JSON object:
134
+ {
135
+ "analysis": "string evaluation",
136
+ "status": "executing" | "completed" | "failed",
137
+ "updatedPlan": { ... same structure as plan ... }
138
+ }`;
139
+
140
+ try {
141
+ const response = await callAI(msg.aiagent, prompt, "You are an AI Orchestrator that reflects on progress.");
142
+ const reflection = JSON.parse(extractJson(response));
143
+
144
+ msg.orchestration.status = reflection.status;
145
+ if (reflection.updatedPlan) {
146
+ msg.orchestration.plan = reflection.updatedPlan;
147
+ }
148
+ } catch (error) {
149
+ node.warn(`Reflection failed, continuing with current plan: ${error.message}`);
150
+ // Fallback: just move to next task if possible
151
+ }
152
+ }
153
+
154
+ function getNextTask(plan) {
155
+ if (!plan || !plan.tasks) return null;
156
+ return plan.tasks.find(t => t.status === 'pending');
157
+ }
158
+
159
+ async function callAI(aiConfig, prompt, systemPrompt) {
160
+ const response = await axios.post(
161
+ 'https://openrouter.ai/api/v1/chat/completions',
162
+ {
163
+ model: aiConfig.model,
164
+ messages: [
165
+ { role: 'system', content: systemPrompt },
166
+ { role: 'user', content: prompt }
167
+ ],
168
+ response_format: { type: 'json_object' }
169
+ },
170
+ {
171
+ headers: {
172
+ 'Authorization': `Bearer ${aiConfig.apiKey}`,
173
+ 'Content-Type': 'application/json'
174
+ }
175
+ }
176
+ );
177
+ return response.data.choices[0]?.message?.content || '';
178
+ }
179
+
180
+ function extractJson(text) {
181
+ const match = text.match(/\{[\s\S]*\}/);
182
+ return match ? match[0] : text;
183
+ }
184
+
185
+ RED.nodes.registerType('ai-orchestrator', AiOrchestratorNode);
186
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-ai-agent",
3
- "version": "0.0.7",
3
+ "version": "0.2.0",
4
4
  "description": "AI Agent for Node-RED",
5
5
  "repository": {
6
6
  "type": "git",
@@ -32,7 +32,8 @@
32
32
  "tool-function/*",
33
33
  "tool-http/*",
34
34
  "memory-file/*",
35
- "memory-inmem/*"
35
+ "memory-inmem/*",
36
+ "orchestrator/*"
36
37
  ],
37
38
  "dependencies": {
38
39
  "axios": "^1.6.0",
@@ -60,7 +61,8 @@
60
61
  "ai-tool-function": "./tool-function/ai-tool-function.js",
61
62
  "ai-tool-http": "./tool-http/ai-tool-http.js",
62
63
  "ai-memory-file": "./memory-file/memory-file.js",
63
- "ai-memory-inmem": "./memory-inmem/memory-inmem.js"
64
+ "ai-memory-inmem": "./memory-inmem/memory-inmem.js",
65
+ "ai-orchestrator": "./orchestrator/orchestrator.js"
64
66
  }
65
67
  }
66
68
  }