This workflow corresponds to n8n.io template #15687 — we link there as the canonical source.
This workflow follows the Agent → Google Sheets recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "9qaAJYL2GGTvDyYu",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Autonomous Multi-Supplier Price Comparison Agent (Agent Q)",
"tags": [],
"nodes": [
{
"id": "558bedee-9497-45e9-90af-b08d78f9c115",
"name": "\ud83d\udccb Trigger \u2014 New Comparison Request",
"type": "n8n-nodes-base.googleSheetsTrigger",
"notes": "Triggers when a new purchase request row is added to Google Sheets",
"position": [
-1040,
784
],
"parameters": {
"event": "rowAdded",
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"sheetName": {
"value": "Sheet1",
"cachedResultName": "Sheet1"
},
"documentId": {
"value": "YOUR_GOOGLE_SHEET_ID",
"cachedResultName": "Purchase Requests Sheet"
}
},
"credentials": {
"googleSheetsTriggerOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "be9342e7-7d3e-4cae-a059-08c5ef9b64e9",
"name": "\ud83d\uddfa\ufe0f Parse Request & Plan MCTS Navigation",
"type": "n8n-nodes-base.code",
"notes": "Validates input and runs MCTS pre-planning for each supplier site navigation path",
"position": [
-816,
784
],
"parameters": {
"jsCode": "// Extract and validate purchase request data\nconst row = $input.first().json;\n\nconst productName = row['Product Name'] || row['product_name'] || row['Product'];\nconst quantityNeeded = row['Quantity'] || row['quantity_needed'] || row['Qty'];\nconst requestedBy = row['Requested By'] || row['requestor'] || 'Unknown';\nconst requestId = row['Request ID'] || `REQ-${Date.now()}`;\nconst requestDate = row['Date'] || new Date().toISOString();\nconst budget = row['Budget'] || row['budget'] || 'Not specified';\nconst notes = row['Notes'] || row['notes'] || '';\n\nif (!productName || !quantityNeeded) {\n throw new Error('Missing required fields: Product Name or Quantity');\n}\n\n// MCTS: Pre-plan navigation strategy for 5 supplier websites\n// Monte Carlo Tree Search approach: plan before acting\nconst supplierNavigationPlan = [\n {\n supplierId: 'supplier_1',\n name: 'Global Industrial',\n baseUrl: 'https://www.globalindustrial.com',\n searchUrl: `https://www.globalindustrial.com/c/search?q=${encodeURIComponent(productName)}`,\n navigationPath: ['search_bar', 'first_result', 'product_page'],\n priceSelector: '.price, .product-price, [class*=\"price\"]',\n moqSelector: '.min-order, .minimum-order, [class*=\"moq\"]',\n deliverySelector: '.delivery, .shipping-time, [class*=\"delivery\"]',\n stockSelector: '.stock, .availability, [class*=\"stock\"]',\n planConfidence: 0.85\n },\n {\n supplierId: 'supplier_2',\n name: 'Alibaba',\n baseUrl: 'https://www.alibaba.com',\n searchUrl: `https://www.alibaba.com/trade/search?SearchText=${encodeURIComponent(productName)}`,\n navigationPath: ['search_results', 'filter_verified', 'product_page'],\n priceSelector: '.price-range, .product-price',\n moqSelector: '.min-order',\n deliverySelector: '.delivery-time',\n stockSelector: '.stock-info',\n planConfidence: 0.80\n },\n {\n supplierId: 'supplier_3',\n name: 'Grainger',\n baseUrl: 'https://www.grainger.com',\n searchUrl: `https://www.grainger.com/search?searchQuery=${encodeURIComponent(productName)}`,\n navigationPath: ['search_bar', 'product_listing', 'product_detail'],\n priceSelector: '.price, .unit-price',\n moqSelector: '.min-quantity, .pack-size',\n deliverySelector: '.ship-date, .availability-message',\n stockSelector: '.in-stock, .availability',\n planConfidence: 0.88\n },\n {\n supplierId: 'supplier_4',\n name: 'Amazon Business',\n baseUrl: 'https://www.amazon.com',\n searchUrl: `https://www.amazon.com/s?k=${encodeURIComponent(productName)}&i=industrial`,\n navigationPath: ['search_results', 'filter_business', 'product_page'],\n priceSelector: '.a-price-whole, #priceblock_ourprice',\n moqSelector: '.quantity-selector, .min-order-qty',\n deliverySelector: '.delivery-message, #deliveryMessageMirId',\n stockSelector: '.availability, #availability',\n planConfidence: 0.90\n },\n {\n supplierId: 'supplier_5',\n name: 'McMaster-Carr',\n baseUrl: 'https://www.mcmaster.com',\n searchUrl: `https://www.mcmaster.com/search/${encodeURIComponent(productName)}`,\n navigationPath: ['search_results', 'product_listing', 'price_table'],\n priceSelector: '.price, .PriceDisplay',\n moqSelector: '.min-qty, .PackageSize',\n deliverySelector: '.availability, .DeliveryDate',\n stockSelector: '.InStock, .StockStatus',\n planConfidence: 0.87\n }\n];\n\nreturn {\n requestId,\n productName,\n quantityNeeded: parseInt(quantityNeeded),\n requestedBy,\n requestDate,\n budget,\n notes,\n supplierNavigationPlan,\n status: 'initialized',\n timestamp: new Date().toISOString()\n};"
},
"typeVersion": 2
},
{
"id": "338de0e6-0f9b-4e72-a1e6-8dee85136dde",
"name": "\ud83e\udd16 Agent Q \u2014 Supplier Research Orchestrator",
"type": "@n8n/n8n-nodes-langchain.agent",
"notes": "Agent Q with MCTS planning - orchestrates all supplier research",
"position": [
-592,
784
],
"parameters": {
"options": {
"systemMessage": "You are Agent Q \u2014 an autonomous procurement intelligence agent. You operate with two cognitive modes: MCTS (Monte Carlo Tree Search) for pre-planned navigation, and Self-Critique for data validation. You never hallucinate prices. If you cannot retrieve real data, you say so explicitly.\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nIDENTITY & CONSTRAINTS\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n- You are a research-only agent. You do not place orders or contact suppliers.\n- You always return a single, valid JSON object \u2014 no preamble, no markdown fences, no explanation outside the JSON.\n- If a supplier page is unreachable or returns no usable data, set navigationSuccess: false and unitPrice: null for that supplier. Never fabricate a price.\n- All prices must be in USD. If a price is in another currency, convert using approximate rates and note it in agentNotes.\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nPHASE 1 \u2014 MCTS NAVIGATION PLANNING\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nBefore opening any supplier URL, reason through the navigation tree for each supplier:\n1. Primary path: what URL to hit, what element to find, what selector to try\n2. Fallback path: if primary fails (404, CAPTCHA, no product found), what is the next best action?\n3. Confidence score: estimate 0.0\u20131.0 how likely this plan succeeds before executing\n4. If confidence < 0.65 for a supplier, note it in agentNotes and attempt anyway\n\nPlan all 5 suppliers before visiting any of them.\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nPHASE 2 \u2014 SUPPLIER VISITS\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nFor each of the 5 suppliers, execute in order:\n\nStep 1: Fetch the search results page using the provided searchUrl\nStep 2: Identify the product listing that best matches the product name and quantity context\nStep 3: Fetch that product's detail page\nStep 4: Extract:\n - unitPrice (number, USD, price per single unit)\n - priceAtQuantity (number, USD, total at the requested quantity \u2014 check for bulk discounts)\n - moq (number, minimum order quantity)\n - deliveryDays (number, estimated business days to ship)\n - stockStatus: one of \"in_stock\" | \"low_stock\" | \"out_of_stock\" | \"unknown\"\n - productUrl (the exact URL of the product page you extracted from)\n - rawPriceText (the raw text you found before parsing, e.g. \"$1.24 / each\")\n - dataConfidence (0.0\u20131.0, how certain you are this is the correct product and price)\n\nIf the product has tiered pricing, use the tier that applies to the requested quantity.\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nPHASE 3 \u2014 SELF-CRITIQUE VALIDATION\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nAfter collecting all supplier data, run the following checks:\n\n1. OUTLIER CHECK\n - Calculate mean price from all suppliers with valid prices\n - Calculate standard deviation\n - Flag any price where |price - mean| > 1.5 \u00d7 stdDev as anomalous\n - For flagged prices: set anomalyFlag: true and describe anomalyReason\n - Do NOT exclude anomalous prices from the output \u2014 flag them and let the human decide\n\n2. SANITY CHECKS\n - MOQ must be a positive integer. If extracted as a range (e.g. \"50\u2013100\"), use the lower bound.\n - Delivery days must be a positive integer. If expressed as weeks, multiply by 7. If a range, use the midpoint.\n - Unit price must be > 0. If a page shows \"$0.00\" or \"call for price\", set unitPrice: null and navigationSuccess: false.\n\n3. DATA QUALITY SCORE (0\u2013100)\n - Start at 100\n - Subtract 15 for each supplier with navigationSuccess: false\n - Subtract 10 for each anomalyFlag: true\n - Subtract 5 for each dataConfidence < 0.7\n - Minimum score: 0\n\n4. CRITIQUE SUMMARY\n Write 1\u20132 sentences summarizing data quality, any anomalies, and whether human review is recommended.\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nPHASE 4 \u2014 RANKING\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nRank suppliers using only suppliers where navigationSuccess: true and unitPrice is not null.\n\nBEST PRICE: Supplier with lowest unitPrice at the requested quantity\nBEST DELIVERY: Supplier with lowest deliveryDays and stockStatus !== \"out_of_stock\"\nBEST OVERALL: Weighted composite score per supplier:\n score = (1 / unitPrice \u00d7 0.60) + (1 / deliveryDays \u00d7 0.25) + (dataConfidence \u00d7 0.15)\n Normalize each component to 0\u20131 range before weighting.\n bestOverallScore = the winning supplier's normalized composite (0\u2013100).\n\nIf fewer than 3 suppliers have valid data, note this in agentNotes and rank from available data only.\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nOUTPUT FORMAT (return ONLY this JSON)\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n{\n \"requestId\": \"string\",\n \"productName\": \"string\",\n \"quantityNeeded\": number,\n \"researchTimestamp\": \"ISO 8601 string\",\n \"suppliers\": [\n {\n \"supplierId\": \"string\",\n \"supplierName\": \"string\",\n \"productUrl\": \"string or null\",\n \"unitPrice\": number or null,\n \"currency\": \"USD\",\n \"priceAtQuantity\": number or null,\n \"moq\": number or null,\n \"deliveryDays\": number or null,\n \"stockStatus\": \"in_stock | low_stock | out_of_stock | unknown\",\n \"anomalyFlag\": boolean,\n \"anomalyReason\": \"string or null\",\n \"dataConfidence\": number,\n \"rawPriceText\": \"string or null\",\n \"navigationSuccess\": boolean\n }\n ],\n \"rankings\": {\n \"bestPrice\": \"supplierName or null\",\n \"bestDelivery\": \"supplierName or null\",\n \"bestOverall\": \"supplierName or null\",\n \"bestOverallScore\": number\n },\n \"selfCritiqueReport\": {\n \"meanPrice\": number or null,\n \"priceStdDev\": number or null,\n \"anomaliesDetected\": number,\n \"dataQualityScore\": number,\n \"critiqueSummary\": \"string\"\n },\n \"agentNotes\": \"string\"\n}"
}
},
"typeVersion": 1.7
},
{
"id": "1df03776-c083-48ff-808a-279f82e46eaa",
"name": "\ud83c\udf10 HTTP \u2014 Fetch Supplier Page",
"type": "n8n-nodes-base.httpRequest",
"notes": "Tool: Fetches supplier web pages for Agent Q",
"position": [
-16,
784
],
"parameters": {
"url": "={{ $fromAI('url', 'The URL to fetch for supplier research') }}",
"options": {
"timeout": 15000,
"response": {
"response": {
"responseFormat": "text"
}
}
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "User-Agent",
"value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
},
{
"name": "Accept",
"value": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "a32be05b-177d-429d-95e8-4952cb155576",
"name": "\ud83d\udcca Parse & Score Agent Q Research Output",
"type": "n8n-nodes-base.code",
"notes": "Parses and validates Agent Q's JSON research results",
"position": [
-240,
784
],
"parameters": {
"jsCode": "// Parse Agent Q's JSON response from the LLM\nconst agentOutput = $input.first().json.output || $input.first().json.text || $input.first().json;\n\nlet researchData;\ntry {\n // Handle string or object output\n if (typeof agentOutput === 'string') {\n // Strip markdown code fences if present\n const cleaned = agentOutput.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n researchData = JSON.parse(cleaned);\n } else {\n researchData = agentOutput;\n }\n} catch (e) {\n // Fallback: create a structured error response\n researchData = {\n requestId: $('Parse Request + MCTS Navigation Planning').first().json.requestId,\n productName: $('Parse Request + MCTS Navigation Planning').first().json.productName,\n quantityNeeded: $('Parse Request + MCTS Navigation Planning').first().json.quantityNeeded,\n researchTimestamp: new Date().toISOString(),\n error: 'Failed to parse agent output',\n rawOutput: agentOutput.toString().substring(0, 500),\n suppliers: [],\n rankings: { bestPrice: 'N/A', bestDelivery: 'N/A', bestOverall: 'N/A', bestOverallScore: 0 },\n selfCritiqueReport: { dataQualityScore: 0, critiqueSummary: 'Parse error occurred' },\n agentNotes: 'Error in parsing'\n };\n}\n\n// Enrich with original request metadata\nconst originalRequest = $('Parse Request + MCTS Navigation Planning').first().json;\nresearchData.requestedBy = originalRequest.requestedBy;\nresearchData.requestDate = originalRequest.requestDate;\nresearchData.budget = originalRequest.budget;\nresearchData.processingComplete = true;\n\n// Generate Notion-ready formatted blocks\nresearchData.notionFormatted = {\n title: `Price Comparison: ${researchData.productName} \u2014 ${researchData.requestId}`,\n subtitle: `Requested by ${researchData.requestedBy} \u2022 ${new Date(researchData.requestDate).toLocaleDateString()} \u2022 Qty: ${researchData.quantityNeeded}`,\n qualityBadge: researchData.selfCritiqueReport?.dataQualityScore >= 80 ? '\u2705 High Quality Data' : researchData.selfCritiqueReport?.dataQualityScore >= 60 ? '\u26a0\ufe0f Medium Quality' : '\ud83d\udd34 Low Quality \u2014 Review Manually'\n};\n\nreturn researchData;"
},
"typeVersion": 2
},
{
"id": "3b5bc79f-b3da-4071-a957-8a39139301c5",
"name": "\u270d\ufe0f Generate Negotiation Message",
"type": "n8n-nodes-base.code",
"notes": "Auto-drafts opening negotiation message for top-ranked supplier",
"position": [
208,
784
],
"parameters": {
"jsCode": "// Generate the negotiation message for the top-ranked supplier\nconst data = $input.first().json;\n\nconst bestOverallName = data.rankings?.bestOverall || 'N/A';\nconst bestSupplier = data.suppliers?.find(s => s.supplierName === bestOverallName);\n\nif (!bestSupplier) {\n return {\n ...data,\n negotiationMessage: {\n subject: `Price Inquiry \u2014 ${data.productName}`,\n body: 'Unable to generate negotiation message \u2014 supplier data unavailable.',\n generated: false\n }\n };\n}\n\n// Calculate target price (10% below best price)\nconst targetPrice = (bestSupplier.unitPrice * 0.90).toFixed(2);\nconst totalOrderValue = (bestSupplier.unitPrice * data.quantityNeeded).toFixed(2);\nconst targetTotal = (targetPrice * data.quantityNeeded).toFixed(2);\n\n// Determine competitor leverage\nconst competitors = data.suppliers\n .filter(s => s.supplierName !== bestOverallName && s.navigationSuccess && !s.anomalyFlag)\n .sort((a, b) => a.unitPrice - b.unitPrice)\n .slice(0, 2)\n .map(s => `${s.supplierName} at $${s.unitPrice?.toFixed(2)}/unit`)\n .join(' and ');\n\nconst negotiationMessage = {\n subject: `Bulk Order Inquiry \u2014 ${data.productName} (${data.quantityNeeded} units)`,\n body: `Dear ${bestOverallName} Sales Team,\n\nI hope this message finds you well. We are evaluating suppliers for a purchase of ${data.quantityNeeded} units of ${data.productName} (Request ID: ${data.requestId}).\n\nYour current listed price of $${bestSupplier.unitPrice?.toFixed(2)}/unit represents a total order value of $${totalOrderValue}. We have also received comparable quotes from ${competitors || 'other suppliers'}, and are positioned to move forward quickly with the right partner.\n\nGiven our order volume and commitment to a prompt purchase decision, we would like to request your best pricing \u2014 ideally at or near $${targetPrice}/unit (approx. $${targetTotal} total).\n\nCould you also confirm:\n1. Delivery timeline to [YOUR LOCATION] for this quantity\n2. Whether bulk pricing tiers apply at this volume\n3. Payment terms available\n\nWe are ready to issue a purchase order within 48 hours for the right offer. Please respond at your earliest convenience.\n\nBest regards,\n${data.requestedBy}\nProcurement Team`,\n targetUnitPrice: parseFloat(targetPrice),\n potentialSaving: parseFloat((bestSupplier.unitPrice - targetPrice) * data.quantityNeeded).toFixed(2),\n generated: true,\n supplierContact: bestSupplier.supplierName\n};\n\nreturn {\n ...data,\n negotiationMessage\n};"
},
"typeVersion": 2
},
{
"id": "e26c096d-d67a-4329-b4a8-ceec5829914d",
"name": "\ud83d\udcd2 Create Notion Comparison Page",
"type": "n8n-nodes-base.notion",
"notes": "Builds ranked comparison table in Notion with all supplier data and negotiation message",
"position": [
432,
784
],
"parameters": {
"title": "=Price Comparison: {{ $json.productName }} \u2014 {{ $json.requestId }}",
"pageId": {
"__rl": true,
"mode": "url",
"value": "https://www.notion.so/Test-n8n-automation-350f839f118480fb9bddfb4278f2ae4e"
},
"blockUi": {
"blockValues": [
{
"type": "heading_2",
"textContent": "=\ud83d\udcca Supplier Price Comparison \u2014 {{ $json.productName }}"
},
{
"textContent": "={{ $json.notionFormatted.subtitle }}"
},
{
"textContent": "={{ $json.notionFormatted.qualityBadge }}"
},
{
"type": "heading_3",
"textContent": "\ud83c\udfc6 Rankings Summary"
},
{
"textContent": "=\ud83e\udd47 Best Price: {{ $json.rankings.bestPrice }}\n\ud83d\ude9a Best Delivery: {{ $json.rankings.bestDelivery }}\n\u2b50 Best Overall: {{ $json.rankings.bestOverall }} (Score: {{ $json.rankings.bestOverallScore }})"
},
{
"type": "heading_3",
"textContent": "\ud83d\udccb Detailed Supplier Data"
},
{
"textContent": "={{ $json.suppliers.map(s => `**${s.supplierName}**\\nUnit Price: $${s.unitPrice?.toFixed(2)} | MOQ: ${s.moq} | Delivery: ${s.deliveryDays} days | Stock: ${s.stockStatus} | Confidence: ${Math.round(s.dataConfidence * 100)}%${s.anomalyFlag ? ' \u26a0\ufe0f ANOMALY: ' + s.anomalyReason : ''}`).join('\\n\\n') }}"
},
{
"type": "heading_3",
"textContent": "\ud83d\udd0d Self-Critique Report"
},
{
"textContent": "={{ `Mean Price: $${$json.selfCritiqueReport.meanPrice?.toFixed(2)}\\nStd Deviation: $${$json.selfCritiqueReport.priceStdDev?.toFixed(2)}\\nAnomalies Detected: ${$json.selfCritiqueReport.anomaliesDetected}\\nData Quality: ${$json.selfCritiqueReport.dataQualityScore}/100\\n\\n${$json.selfCritiqueReport.critiqueSummary}` }}"
},
{
"type": "heading_3",
"textContent": "\u2709\ufe0f Draft Negotiation Message"
},
{
"textContent": "=**Subject:** {{ $json.negotiationMessage.subject }}"
},
{
"textContent": "={{ $json.negotiationMessage.body }}"
},
{
"textContent": "=\ud83d\udcb0 Potential Saving if Target Achieved: ${{ $json.negotiationMessage.potentialSaving }}"
},
{
"type": "heading_3",
"textContent": "\ud83e\udd16 Agent Notes"
},
{
"textContent": "={{ $json.agentNotes }}"
}
]
},
"options": {}
},
"credentials": {
"notionApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "721d288c-d05f-46f3-baed-c572068f390e",
"name": "\ud83d\udcdd Format Slack Summary",
"type": "n8n-nodes-base.code",
"notes": "Formats rich Slack message with comparison summary and Notion link",
"position": [
656,
688
],
"parameters": {
"jsCode": "// Format the Slack message for procurement manager\nconst data = $input.first().json;\nconst notionPageId = $('Create Notion Comparison Page').first().json.id;\nconst notionUrl = `https://notion.so/${notionPageId?.replace(/-/g, '')}`;\n\nconst bestSupplier = data.suppliers?.find(s => s.supplierName === data.rankings?.bestOverall);\nconst bestPriceSupplier = data.suppliers?.find(s => s.supplierName === data.rankings?.bestPrice);\nconst bestDeliverySupplier = data.suppliers?.find(s => s.supplierName === data.rankings?.bestDelivery);\n\nconst supplierRows = data.suppliers\n ?.filter(s => s.navigationSuccess)\n .sort((a, b) => a.unitPrice - b.unitPrice)\n .map((s, i) => {\n const medal = i === 0 ? '\ud83e\udd47' : i === 1 ? '\ud83e\udd48' : i === 2 ? '\ud83e\udd49' : ' ';\n const flag = s.anomalyFlag ? ' \u26a0\ufe0f' : '';\n return `${medal} *${s.supplierName}*${flag} \u2014 $${s.unitPrice?.toFixed(2)}/unit | MOQ: ${s.moq} | Delivery: ${s.deliveryDays}d`;\n })\n .join('\\n') || 'No supplier data available';\n\nconst qualityEmoji = data.selfCritiqueReport?.dataQualityScore >= 80 ? '\u2705' : data.selfCritiqueReport?.dataQualityScore >= 60 ? '\u26a0\ufe0f' : '\ud83d\udd34';\n\nconst slackMessage = {\n blocks: [\n {\n type: 'header',\n text: {\n type: 'plain_text',\n text: `\ud83e\udd16 Agent Q \u2014 Price Comparison Complete`,\n emoji: true\n }\n },\n {\n type: 'section',\n fields: [\n { type: 'mrkdwn', text: `*Product:*\\n${data.productName}` },\n { type: 'mrkdwn', text: `*Request ID:*\\n${data.requestId}` },\n { type: 'mrkdwn', text: `*Quantity:*\\n${data.quantityNeeded} units` },\n { type: 'mrkdwn', text: `*Requested By:*\\n${data.requestedBy}` }\n ]\n },\n { type: 'divider' },\n {\n type: 'section',\n text: {\n type: 'mrkdwn',\n text: `*\ud83d\udcca Price Rankings (lowest \u2192 highest):*\\n${supplierRows}`\n }\n },\n { type: 'divider' },\n {\n type: 'section',\n fields: [\n { type: 'mrkdwn', text: `*\ud83c\udfc6 Best Overall:*\\n${data.rankings?.bestOverall}` },\n { type: 'mrkdwn', text: `*\ud83d\udcb0 Best Price:*\\n${data.rankings?.bestPrice} @ $${bestPriceSupplier?.unitPrice?.toFixed(2)}/unit` },\n { type: 'mrkdwn', text: `*\ud83d\ude9a Best Delivery:*\\n${data.rankings?.bestDelivery} (${bestDeliverySupplier?.deliveryDays} days)` },\n { type: 'mrkdwn', text: `*${qualityEmoji} Data Quality:*\\n${data.selfCritiqueReport?.dataQualityScore}/100` }\n ]\n },\n {\n type: 'section',\n text: {\n type: 'mrkdwn',\n text: `*\u2709\ufe0f Negotiation Message:* Draft ready for *${data.negotiationMessage?.supplierContact}* (target: $${data.negotiationMessage?.targetUnitPrice}/unit \u2014 potential saving: *$${data.negotiationMessage?.potentialSaving}*)`\n }\n },\n {\n type: 'actions',\n elements: [\n {\n type: 'button',\n text: { type: 'plain_text', text: '\ud83d\udccb View Full Comparison in Notion', emoji: true },\n url: notionUrl,\n style: 'primary'\n }\n ]\n },\n {\n type: 'context',\n elements: [\n {\n type: 'mrkdwn',\n text: `\u26a1 Research completed by Agent Q in under 3 minutes | ${data.selfCritiqueReport?.anomaliesDetected || 0} price anomalies detected & flagged | ${new Date().toLocaleTimeString()}`\n }\n ]\n }\n ]\n};\n\nreturn { slackPayload: slackMessage, ...data };"
},
"typeVersion": 2
},
{
"id": "7a9e9121-1e97-44d8-a234-8843fd33c6bd",
"name": "\ud83d\udd14 Slack \u2014 Send Comparison Summary",
"type": "n8n-nodes-base.slack",
"notes": "Sends comparison summary to procurement manager on Slack",
"position": [
880,
688
],
"parameters": {
"select": "channel",
"blocksUi": "={{ JSON.stringify($json.slackPayload.blocks) }}",
"channelId": {
"value": "YOUR_SLACK_CHANNEL_ID",
"cachedResultName": "#procurement-team"
},
"messageType": "block",
"otherOptions": {}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "1ffb652c-4d28-45a8-8de1-ed9ddcaf2f92",
"name": "\u2705 Update Google Sheet Status",
"type": "n8n-nodes-base.googleSheets",
"notes": "Updates the original Google Sheet row with completion status and results",
"position": [
1104,
688
],
"parameters": {
"columns": {
"value": {},
"schema": [],
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"value": "Sheet1",
"cachedResultName": "Sheet1"
},
"documentId": {
"value": "YOUR_GOOGLE_SHEET_ID",
"cachedResultName": "Purchase Requests Sheet"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "89a155c4-6930-415a-b14b-64282faa050b",
"name": "\u26a0\ufe0f Anomalies Detected?",
"type": "n8n-nodes-base.if",
"notes": "Routes to anomaly alert if self-critique flagged suspicious prices",
"position": [
656,
880
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "check-anomaly",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.selfCritiqueReport.anomaliesDetected }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.1
},
{
"id": "292663d4-c28d-40f7-8201-99b01f8d4a5c",
"name": "\ud83d\udea8 Slack \u2014 Send Anomaly Alert",
"type": "n8n-nodes-base.slack",
"notes": "Sends anomaly alert when self-critique flags suspicious prices",
"position": [
880,
880
],
"parameters": {
"select": "channel",
"blocksUi": "=[\n {\n \"type\": \"header\",\n \"text\": { \"type\": \"plain_text\", \"text\": \"\u26a0\ufe0f Agent Q \u2014 Price Anomaly Alert\", \"emoji\": true }\n },\n {\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \"*{{ $json.selfCritiqueReport.anomaliesDetected }} anomalous price(s) detected* for *{{ $json.productName }}* ({{ $json.requestId }}).\\n\\nSelf-Critique Summary: {{ $json.selfCritiqueReport.critiqueSummary }}\\n\\n*Flagged suppliers:*\\n{{ $json.suppliers.filter(s => s.anomalyFlag).map(s => `\u2022 ${s.supplierName}: $${s.unitPrice?.toFixed(2)} \u2014 ${s.anomalyReason}`).join('\\n') }}\"\n }\n },\n {\n \"type\": \"section\",\n \"text\": { \"type\": \"mrkdwn\", \"text\": \"Please manually verify flagged prices before approving the comparison.\" }\n }\n]",
"channelId": {
"value": "YOUR_SLACK_CHANNEL_ID",
"cachedResultName": "#procurement-alerts"
},
"messageType": "block",
"otherOptions": {}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "89b166a8-6518-43ff-917a-c14b5d22630f",
"name": "\u26a1 LLM \u2014 Agent Q Brain (GPT-4o)",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-520,
1008
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-5-mini"
},
"options": {},
"builtInTools": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "69811092-66be-4b6e-a7d6-5d63e646d800",
"name": "Sticky Note \u2014 Phase 1: Trigger & Planning",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1232,
592
],
"parameters": {
"color": 3,
"width": 500,
"height": 175,
"content": "## \ud83d\udce5 Phase 1: Trigger & Request Planning\nFired when a new product comparison request is added to Google Sheets.\nParses supplier URLs, product details, and uses **MCTS navigation planning** to map out the research strategy before handing off to Agent Q."
},
"typeVersion": 1
},
{
"id": "cf4d49fe-dcaf-4587-b5e4-5d69c1eddfed",
"name": "Sticky Note \u2014 Phase 2: Agent Q Research",
"type": "n8n-nodes-base.stickyNote",
"position": [
-592,
560
],
"parameters": {
"color": 5,
"width": 600,
"height": 206,
"content": "## \ud83e\udd16 Phase 2: Agent Q \u2014 Autonomous Supplier Research\n**Agent Q** orchestrates intelligent supplier research:\n- Uses GPT-4o as its reasoning brain\n- Fetches live supplier pages via HTTP\n- Parses & scores pricing, lead times, MOQ, and terms\n- Outputs a structured multi-supplier comparison dataset"
},
"typeVersion": 1
},
{
"id": "bfa9ad15-6f3e-434d-8ce6-8cb242d750b0",
"name": "Sticky Note \u2014 Phase 3: Negotiation Message Generation",
"type": "n8n-nodes-base.stickyNote",
"position": [
112,
592
],
"parameters": {
"color": 6,
"width": 430,
"height": 155,
"content": "## \u270d\ufe0f Phase 3: Auto-Generate Negotiation Message\nUsing the best supplier identified from the comparison,\nan AI-crafted negotiation message is generated with targeted pricing asks and leverage points."
},
"typeVersion": 1
},
{
"id": "c3134c14-0ebc-4e50-9975-bca99f5945da",
"name": "Sticky Note \u2014 Phase 4: Logging & Notifications",
"type": "n8n-nodes-base.stickyNote",
"position": [
736,
480
],
"parameters": {
"color": 7,
"width": 500,
"content": "## \ud83d\udcca Phase 4: Logging & Team Notifications\n- Creates a full **Notion comparison page** with supplier breakdown\n- Formats and sends a **Slack summary** to the procurement team\n- Updates **Google Sheets** row with final status & best supplier"
},
"typeVersion": 1
},
{
"id": "9de47f40-a819-4cc1-8f3d-4422b48a468e",
"name": "Sticky Note \u2014 Phase 5: Anomaly Detection",
"type": "n8n-nodes-base.stickyNote",
"position": [
832,
1072
],
"parameters": {
"color": 2,
"width": 470,
"height": 165,
"content": "## \u26a0\ufe0f Phase 5: Anomaly Detection\nChecks for price spikes, unusually long lead times, or MOQ outliers across suppliers.\n- \u2705 **No anomalies** \u2192 Flow ends normally\n- \ud83d\udea8 **Anomaly found** \u2192 Sends an urgent **Slack alert** to flag the issue for manual review"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "7c4211f4-c384-46ec-a0af-3861babca5a1",
"connections": {
"\ud83d\udcdd Format Slack Summary": {
"main": [
[
{
"node": "\ud83d\udd14 Slack \u2014 Send Comparison Summary",
"type": "main",
"index": 0
}
]
]
},
"\u26a0\ufe0f Anomalies Detected?": {
"main": [
[
{
"node": "\ud83d\udea8 Slack \u2014 Send Anomaly Alert",
"type": "main",
"index": 0
}
]
]
},
"\ud83c\udf10 HTTP \u2014 Fetch Supplier Page": {
"main": [
[
{
"node": "\u270d\ufe0f Generate Negotiation Message",
"type": "main",
"index": 0
}
]
]
},
"\u26a1 LLM \u2014 Agent Q Brain (GPT-4o)": {
"ai_languageModel": [
[
{
"node": "\ud83e\udd16 Agent Q \u2014 Supplier Research Orchestrator",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"\ud83d\udcd2 Create Notion Comparison Page": {
"main": [
[
{
"node": "\ud83d\udcdd Format Slack Summary",
"type": "main",
"index": 0
},
{
"node": "\u26a0\ufe0f Anomalies Detected?",
"type": "main",
"index": 0
}
]
]
},
"\u270d\ufe0f Generate Negotiation Message": {
"main": [
[
{
"node": "\ud83d\udcd2 Create Notion Comparison Page",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd14 Slack \u2014 Send Comparison Summary": {
"main": [
[
{
"node": "\u2705 Update Google Sheet Status",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udccb Trigger \u2014 New Comparison Request": {
"main": [
[
{
"node": "\ud83d\uddfa\ufe0f Parse Request & Plan MCTS Navigation",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcca Parse & Score Agent Q Research Output": {
"main": [
[
{
"node": "\ud83c\udf10 HTTP \u2014 Fetch Supplier Page",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\uddfa\ufe0f Parse Request & Plan MCTS Navigation": {
"main": [
[
{
"node": "\ud83e\udd16 Agent Q \u2014 Supplier Research Orchestrator",
"type": "main",
"index": 0
}
]
]
},
"\ud83e\udd16 Agent Q \u2014 Supplier Research Orchestrator": {
"main": [
[
{
"node": "\ud83d\udcca Parse & Score Agent Q Research Output",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleSheetsOAuth2ApigoogleSheetsTriggerOAuth2ApinotionApiopenAiApislackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Streamline supplier sourcing and decision-making with this AI-powered price comparison automation 🤖. This workflow intelligently researches multiple suppliers in real-time, extracts pricing, delivery timelines, and MOQ data 🔍, and ranks the best options using advanced scoring…
Source: https://n8n.io/workflows/15687/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Automate your entire supplier negotiation process with this AI-driven workflow that intelligently drafts, refines, and sends negotiation emails 📧. Using advanced LLM reasoning and multi-angle strategi
This n8n workflow orchestrates a powerful suite of AI Agents and automations to manage and optimize various aspects of an e-commerce operation, particularly for platforms like Shopify. It leverages La
This automation is designed to help you generate AI-powered music tracks, cover art, and fully rendered music videos — all triggered from a simple Telegram chat and managed via Google Sheets.
This workflow is designed for marketers, content creators, agencies, and solo founders who want to publish long‑form posts with visuals on autopilot using n8n and AI agents.
Stop manually sending follow-ups. This workflow automates your entire cold email outreach with AI-powered personalization, smart scheduling, and automatic reply detection.