Skip to main content

AI Agent Actions

In this chapter, we will build flows that allow the AI Agent to execute selected actions inside Node-RED. The AI Agent determines the appropriate action based on user input and then sends the action request to Node-RED for execution.

Overview of AI Agent Action Execution

The execution of an action consists of the following steps:

  1. AI Agent selects an action based on the user request.
  2. The selected action is sent to Node-RED as a structured request.
  3. A Switch Node determines the action name and routes the payload to the corresponding flow.
  4. The corresponding flow processes the request (e.g., fetching order status, applying a discount).
  5. Once execution is completed, Node-RED sends the result back to the AI Agent.
  6. The AI Agent determines the next steps and provides a final response to the user.

Designing the Action Execution Flow

We will use a Switch Node in Node-RED to route the action request to the appropriate flow.

This Node-RED flow is responsible for handling AI Agent actions by routing requests and executing corresponding workflows.

Node-RED Flow for AI Agent with Actions
Flow JSON of AI Agent with Actions
[
{
"id": "6b3987c0b7f55965",
"type": "function",
"z": "465c30b32ffb6826",
"name": "Prepare OpenAI Request",
"func": "const apiKey = \"sk-proj-o...\";\n\nmsg.headers = {\n \"Authorization\": `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\"\n};\nmsg.input = msg.payload.input;\n\n// Retrieve conversation history (or create an empty array if none exists)\nlet history = global.get(\"chat_history\") || [];\n\n// Add the latest user input\nhistory.push({ role: \"user\", content: msg.payload.input });\n\n// Limit the history size to avoid excessive token usage (e.g., keep last 5 messages)\nif (history.length > 5) history.shift();\n\nmsg.current_date = new Date();\n\nmsg.agentState = global.get(\"agentState\") || {};\n\nmsg.agentState = JSON.stringify({\n ...msg.agentState\n});\n\nlet actionsInfo = `\n<available_actions>\n### Available Actions and When to Use Them:\n1. add_address_information\n<add_address_information>\n - When to Use: This action should be used when the customer provides any contract information that needs to be saved. Whenever the user mentions contact number, email or any other related information, use this action to save the provided details to the address_info field of the order session. Important to use this action before completing the order session\n - Use this action when user provide an email to be contacted\n - Important: output should be clean JSON object which can be parsed.\n - Example Payload: \n {\n \"action\": \"add_address_information\",\n \"payload\": {\n \"phoneNumber\": \"+380678623536\",\n \"contactPerson\": \"Andrii Bidochko\",\n \"email\": \"example@gmail.com\",\n \"notes\": \"Notes about client needs, like what services client needs, etc...\"\n }\n }\n</add_address_information>\n</available_actions>\n`;\n\n// Construct the payload with memory\nmsg.payload = {\n model: \"gpt-4\",\n messages: [\n {\n \"role\": \"system\", \"content\": `\nYou are an AI agent embodying a great and polite salesperson who is always happy to help with any question. \nYour main objective is to assist clients courteously while effectively upselling to increase the order size and enhance company profitability.\n- Warmly greet clients and make them feel valued.\n- Use positive and polite language throughout all interactions.\n- Be attentive to client questions and provide clear, helpful answers.\n- Identify opportunities to suggest additional or upgraded products.\n- Present upsell options as beneficial solutions to the client's needs.\n- Emphasize the added value and advantages of extra products or services.\n- Maintain a friendly and professional demeanor focused on client satisfaction and increased sales.\n\nYour task is to interact with a customer to assist them in:\n- Providing information about products, offering consulting, and providing links to product details.\n- Tracking order delivery status and providing order history, always requiring the customer to provide their CPF (Cadastro de Pessoas Físicas) number or order ID to identify them and retrieve their order information.\n\n<interaction_guidelines>\n### Interaction Guidelines\n* Use Only Available Products: Never generate or assume product details such as names, codes, or IDs. All product information must be taken strictly from the available_products list. If a product is not available, inform the customer or use the load_more_products action to find more options.\n* Ask Questions One by One: Avoid overwhelming the customer with multiple questions at once. Ask questions sequentially to keep the conversation focused and clear.\n* Be Proactive: Take the lead by suggesting products based on user preferences and ensuring all essential information is gathered for order completion.\n* Confirm Details: Summarize and confirm details periodically to avoid misunderstandings and make sure the customer is satisfied.\n* Stay Focused on the Goal: Your task is to collect all the necessary information and finalize the order. Prompt the customer whenever there are missing details or additional products they may need.\n* Maintain Context When Editing Orders: If the user indicates they want to edit an existing order, use the load_order_to_edit action to load the last order from history and make it the current active order. All subsequent actions should be related to editing that specific order, not creating a new one.\n* Ensure All Information is Collected: Make sure that all required information,\n* Respond in User's Language: Always respond in the same language in which the user wrote a message, preferably Portuguese.\n</interaction_guidelines>\n\n<order_process_steps>\nThe process should follow these steps:\n1.Product Information and Consulting: Assist the customer by providing detailed information about products. Understand their needs and preferences, and recommend products accordingly. Provide links to product pages when appropriate.\n2.Order Tracking and History:\nWhen User Provides Order ID: If the customer provides an order ID, use the track_order_delivery_status action immediately to retrieve the delivery status of that specific order.\nWhen User Provides CPF: If the customer provides their CPF number, first use the get_order_history action to retrieve their order history. Then, use the track_order_delivery_status action to track each order's delivery status separately.\n3.Provide Information: After retrieving the required information using the CPF or order ID, provide the customer with the current delivery status, order details, or their order history, as appropriate.\n4.Additional Assistance: Ask if the customer needs any further assistance or has any other questions.\n</order_process_steps>\n\n${actionsInfo}\n\n<guidelines_for_action_determination>\nGuidelines for Action Determination:\n- When User Provides Order ID: If the customer provides an order ID, use the track_order_delivery_status action immediately to retrieve the delivery status of that specific order.\n- When User Provides CPF: If the customer provides their CPF number, first use the get_order_history action to retrieve their order history.Then, use the track_order_delivery_status action to track each order's delivery status separately.\n- Always Request Necessary Information: If the customer wants to track an order or view order history but hasn't provided the required CPF number or order ID, politely ask for it.\n- Analyze Chat History: Carefully analyze the chat_history for any relevant keywords or phrases indicating a particular action should be taken. Only consider the most recent action request that has not been addressed yet.\n- Be Proactive: If the customer seems unsure, offer assistance by suggesting products, providing more information, or guiding them through the process of tracking their order or viewing their order history.\n</guidelines_for_action_determination>\n\n<response_guidelines>\nStarting below, you must follow this format and respond in valid JSON object:\n{\n\"Thought\": \"Provide a thoughts explaining why the action was selected and how it will help advance the order process. You should always think about what to do on current stage\",\n\"UserMessage\": \"Provide a message to the user if needed. Important to understand that after action is executed you will have a chance to generate message after action execution. So most of the time it is better to perform an action and send user message after. Sometimes you can just perform an action and send message after action will be executed. Please send message to user just when you have important information or question in other cases, please leave this field empty\"\n\"SelectedActionJSON\": \"JSON Object of selected action\"\n}\n</response_guidelines>\n\n\nYou will be provided with a JSON object representing the order session and current state, which includes details such as language, currency and list of available products. \nCurrent time is ${msg.current_date}\nJSON object representing your current state: ${msg.agentState}\n `\n },\n {\n \"role\": \"user\", \"content\": `\nChat history information, chat_history:\n<chat_history>\n${history}\n</chat_history>\nWhen working with chat history, follow these important rules: \n- Your goal is to efficiently advance the order process by interacting with the customer and ensuring that all necessary details, particularly delivery information, are collected before finalizing the order.\n- Make sure that address_info in JSON object representing the order session is NOT empty, make sure that you have all address_info of the client to deliver the order before complete the order. Use action add_address_information to save address_info for the user as soon as data will be provided by user. Fields that should be in address_info object phoneNumber, email\n- Order can be completed and closed, only after action complete_order_session was executed and you can find it in chat_history\n- Your main language is English, but you can also respond in the language of the user.\n- You should not perform actions automatically without user request\n- If in the chat_history you see the same request more than 3 times you should stop executing it and report a problem. System can be running in the loop, your job is to be efficient.\n- When as output you produce clean JSON, please do NOT include any comments like \"//\"\n- if in chat_history you see the message with link to the invoice for successfully finished order session, you should deliver that link to the user message\n- last message in chat_history is latest message, you should pay more attention on latest user message\n- in chat_history each new message starts from \"User\" or \"System\"\n- chat_history is a dialog between User and System\n\n\nHere is main user request, use this information to keep focused on what user want:\n${msg.input}\nIMPORTANT: always make sure that you satisfy main user request.\n\nOutput should be valid JSON\nYour main language is English\nOutput: \\n\n ` }\n ],\n temperature: 0.7\n};\n\n// Store the updated history in Node-RED’s global memory\nglobal.set(\"chat_history\", history);\n\nmsg.url = \"https://api.openai.com/v1/chat/completions\";\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 370,
"y": 520,
"wires": [
[
"10b971d0ab39df63"
]
]
},
{
"id": "10b971d0ab39df63",
"type": "http request",
"z": "465c30b32ffb6826",
"name": "Call OpenAI API",
"method": "POST",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 600,
"y": 520,
"wires": [
[
"3d4c52101a4d87a6"
]
]
},
{
"id": "2d146b9a030459ba",
"type": "debug",
"z": "465c30b32ffb6826",
"name": "Open AI Response",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 890,
"y": 480,
"wires": []
},
{
"id": "3d4c52101a4d87a6",
"type": "function",
"z": "465c30b32ffb6826",
"name": "Save LLM response to memory",
"func": "// Retrieve existing chat history (or create an empty array if none exists)\nlet history = global.get(\"chat_history\") || [];\n\n// Extract the AI response from OpenAI’s API output\nlet aiResponse = msg.payload.choices[0].message.content;\n\n// Add AI's response to the conversation history\nhistory.push({ role: \"assistant\", content: aiResponse });\n\n// Limit the history size (keep the last 5 messages)\nif (history.length > 5) history.shift();\n\n// Save updated history to global context\nglobal.set(\"chat_history\", history);\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 850,
"y": 520,
"wires": [
[
"2d146b9a030459ba",
"776b195e34170bff"
]
]
},
{
"id": "776b195e34170bff",
"type": "function",
"z": "465c30b32ffb6826",
"name": "Determine Action",
"func": "msg.parsedPayload = JSON.parse(msg.payload.choices[0].message.content.replace(/```json|```/g, ''));\nmsg.payload = msg.parsedPayload.UserMessage;\n\nif (msg.parsedPayload.SelectedActionJSON != null && msg.parsedPayload.SelectedActionJSON?.action) {\n msg.actionObject = msg.parsedPayload.SelectedActionJSON;\n return msg;\n} \n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1110,
"y": 520,
"wires": [
[
"d9bac197dc49fcf0"
]
]
},
{
"id": "d9bac197dc49fcf0",
"type": "switch",
"z": "465c30b32ffb6826",
"name": "Route Action",
"property": "actionObject.action",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "add_address_information",
"vt": "str"
},
{
"t": "eq",
"v": "action_1",
"vt": "str"
},
{
"t": "eq",
"v": "action_n",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 3,
"x": 1310,
"y": 520,
"wires": [
[
"c6dc5e680ddeca16"
],
[
"98f85e1700ca2d6b"
],
[
"8b04249842af7bcb"
]
]
},
{
"id": "c6dc5e680ddeca16",
"type": "function",
"z": "465c30b32ffb6826",
"name": "add_address_information",
"func": "msg.agentState = global.get(\"agentState\") || {};\n\nmsg.agentState.customer_address_info = msg.actionObject.payload;\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1550,
"y": 460,
"wires": [
[
"914bb3873c440926",
"e3b0881bbc97a77c"
]
]
},
{
"id": "98f85e1700ca2d6b",
"type": "function",
"z": "465c30b32ffb6826",
"name": "action 1",
"func": "\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1500,
"y": 520,
"wires": [
[
"71d7916c2be440b7"
]
]
},
{
"id": "8b04249842af7bcb",
"type": "function",
"z": "465c30b32ffb6826",
"name": "action N",
"func": "\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1500,
"y": 600,
"wires": [
[]
]
},
{
"id": "215c808a2f272150",
"type": "comment",
"z": "465c30b32ffb6826",
"name": "...",
"info": "",
"x": 1490,
"y": 560,
"wires": []
},
{
"id": "651641102e565590",
"type": "link in",
"z": "465c30b32ffb6826",
"name": "link in 1",
"links": [
"914bb3873c440926",
"71d7916c2be440b7",
"3504c08970574514"
],
"x": 275,
"y": 480,
"wires": [
[
"6b3987c0b7f55965"
]
]
},
{
"id": "914bb3873c440926",
"type": "link out",
"z": "465c30b32ffb6826",
"name": "link out 1",
"mode": "link",
"links": [
"651641102e565590"
],
"x": 1715,
"y": 460,
"wires": []
},
{
"id": "71d7916c2be440b7",
"type": "link out",
"z": "465c30b32ffb6826",
"name": "link out 2",
"mode": "link",
"links": [
"651641102e565590"
],
"x": 1595,
"y": 520,
"wires": []
},
{
"id": "3504c08970574514",
"type": "link out",
"z": "465c30b32ffb6826",
"name": "link out 3",
"mode": "link",
"links": [
"651641102e565590"
],
"x": 1595,
"y": 600,
"wires": []
},
{
"id": "e3b0881bbc97a77c",
"type": "debug",
"z": "465c30b32ffb6826",
"name": "Add address debug",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1790,
"y": 420,
"wires": []
},
{
"id": "3ff6898268ad12f9",
"type": "inject",
"z": "465c30b32ffb6826",
"name": "User Request",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"input\":\"Hello my name is Andrew, I live in Ukraine, Kyiv. My phone number is +380678623322\"}",
"payloadType": "json",
"x": 150,
"y": 520,
"wires": [
[
"6b3987c0b7f55965"
]
]
}
]

The flow consists of several key components:

📌 Step 1: User Request Processing

  • Inject Node ("User Request") → Represents the user’s input into the AI system.
  • Function Node ("Prepare OpenAI Request") → Formats the request payload and prepares it for OpenAI’s API.
  • HTTP Request Node ("Call OpenAI API") → Sends the request to OpenAI for processing.
  • Function Node ("Save LLM response to memory") → Stores the AI-generated response into Node-RED’s global memory.

📌 Step 2: Determining the Action

  • Function Node ("Determine Action") → Extracts the action selected by the AI.
    • Reads the AI-generated JSON response.
    • Identifies the selected action from the response.
    • Passes the action and payload to the next step.

📌 Step 3: Routing the Action to the Correct Flow

  • Switch Node ("Route Action") → Routes the request based on the action name.
  • If action = add_address_information → Send to add_address_information flow.
  • If action = action_1 → Send to action_1 flow.
  • If action = action_N → Send to action_N flow (additional actions can be added dynamically).

📌 Step 4: Executing the Selected Action

Each action is processed in its corresponding Function Node, where business logic and processing occur.

  • Function Node ("add_address_information")
    • Extracts user-provided contact details (phone number, email, address, etc.).
    • Stores the data in Node-RED’s global memory.
    • Returns a confirmation response.

📌 Step 5: Finalizing Execution and Triggering AI Agent Again

Once the selected action has been executed, we need to determine the next steps for the AI Agent. Instead of immediately responding to the user, we:

  • Save the execution results into state
    • The result of the executed action is stored in Node-RED’s global memory as AI Agent State.
  • Trigger the AI Agent Again
    • The AI Agent is invoked once more with the updated context.
    • The previous user request, the executed action, and its results are all included in the new AI prompt in the AI Agent State JSON Object.
    • This allows the AI to decide what to do next:
      • Should it ask the user a follow-up question?
      • Should it execute another action?
      • Is there enough information to provide a final response?

Extracting Action Information from LLM Output

Node-RED Flow for AI Agent Action

Once the AI Agent receives a response from OpenAI, we need to parse the output and extract the selected action. This step ensures that Node-RED correctly identifies and processes the AI-selected action.

In this section, we will focus on the Function Node responsible for:

  1. Parsing the LLM’s response.
  2. Extracting the action JSON from the response.
  3. Forwarding the extracted action for execution.

The AI Agent's response follows a structured JSON format, but it may include Markdown formatting (e.g., code blocks) that need to be cleaned and parsed.

Function Node
msg.parsedPayload = JSON.parse(msg.payload.choices[0].message.content.replace(/```json|```/g, ''));
msg.payload = msg.parsedPayload.UserMessage;

if (msg.parsedPayload.SelectedActionJSON != null && msg.parsedPayload.SelectedActionJSON?.action) {
msg.actionObject = msg.parsedPayload.SelectedActionJSON;
return msg;
}

Implementing the add_address_information Action Flow

Node-RED Flow for AI Agent Action

The add_address_information action is responsible for capturing and storing user-provided contact details, such as:

  • Phone number
  • Contact person name
  • Email address
  • Additional notes (e.g., delivery instructions, preferences)

Once the AI Agent selects this action, Node-RED executes the corresponding function, updates the agent's state, and triggers the AI Agent again to determine the next step. The add_address_information action modifies the agentState to store customer address details.

Function Node
msg.agentState = global.get("agentState") || {};

msg.agentState.customer_address_info = msg.actionObject.payload;
return msg;

Test 1: AI Agent Generates an Action Request

This test verifies that the AI Agent correctly processes user input, selects the appropriate action, and updates the Agent's State and History.

We will simulate a user request where they provide personal details (name, location, and phone number).
The expected result is that the AI Agent recognizes this information as relevant for order processing and executes the add_address_information action.

The test begins with the following simulated user request: User Input:

{
"input": "Hello my name is Andrew, I live in Ukraine, Kyiv. My phone number is +380678623322"
}

The AI Agent analyzes the user's message and determines the best course of action. The AI Agent outputs the action request, instructing Node-RED to store the user's contact details. AI Agent Response:

{
"Thought": "The user has provided his name, location, and phone number. This information is necessary for the order process and should be saved. I will use the 'add_address_information' action to store this data. No user message is required at this point as we will first save the information, then confirm it to the user.",
"UserMessage": "",
"SelectedActionJSON":
{
"action": "add_address_information",
"payload":
{
"phoneNumber": "+380678623322",
"contactPerson": "Andrew",
"notes": "User lives in Ukraine, Kyiv."
}
}
}

Once the action is executed, the Agent's State is updated with the stored user information. Updated AI Agent State:

{
"currency":"USD",
"language":"English",
"products":[
{
"name":"Apple",
"price":"5.50"
},
{
"name":"Oranges",
"price":"9.70"
},
{
"name":"Potato",
"price":"2.30"
}
],
"customer_address_info":{
"phoneNumber":"+380678623322",
"contactPerson":"Andrew",
"notes":"User lives in Ukraine, Kyiv."
}
}

The AI Agent maintains conversation context to keep track of past interactions. AI Agent History After Execution:

[
{
"role":"user",
"content":"Hello my name is Andrew, I live in Ukraine, Kyiv. My phone number is +380678623322"
},
{
"role":"assistant",
"content":{
"Thought": "The user has provided his name, location, and phone number. This information is necessary for the order process and should be saved. I will use the 'add_address_information' action to store this data. No user message is required at this point as we will first save the information, then confirm it to the user.",
"UserMessage": "",
"SelectedActionJSON":
{
"action": "add_address_information",
"payload":
{
"phoneNumber": "+380678623322",
"contactPerson": "Andrew",
"notes": "User lives in Ukraine, Kyiv."
}
}
}
}
{
"role":"assistant",
"content":{
"Thought": "The user request is undefined, which means there's no specific user request to fulfill at this moment. In such cases, it's a good idea to engage with the customer and guide them with their shopping needs or inquiries. I will suggest reviewing our product catalog and ask if they have any questions or if they need help with something specific.",
"UserMessage": "Hello! We have a variety of products available. We have Apples priced at $5.50, Oranges for $9.70, and Potatoes for $2.30. Can I assist you with more information about these products or help you with something else?",
"SelectedActionJSON": ""
}
}
]
info

What This History Shows:

🟢 First AI Response

  • The AI detected user-provided contact details.
  • It triggered the add_address_information action without immediately replying.

🟢 Second AI Response

  • Since no further user action was required, the AI proactively engaged with the customer.
  • It suggested checking the product catalog and offered assistance.

Conclusion

This test confirms that:

  1. The AI Agent correctly processes user inputs and determines the best action.
  2. The add_address_information action executes successfully in Node-RED.
  3. The Agent’s memory is updated with the extracted information.
  4. The conversation flow remains natural, engaging the user after task completion.

🚀 Next, we will expand this test to cover additional AI actions, such as retrieving order history and tracking deliveries!