AutomationFlowsAI & RAG › suggestion_generator

suggestion_generator

suggestion_generator. Uses executeWorkflowTrigger, lmChatGroq, agent. Event-driven trigger; 6 nodes.

Event trigger★★★★☆ complexityAI-powered6 nodesExecute Workflow TriggerLm Chat GroqAgent
AI & RAG Trigger: Event Nodes: 6 Complexity: ★★★★☆ AI nodes: yes

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 →

Download .json
{
  "name": "suggestion_generator",
  "nodes": [
    {
      "parameters": {
        "inputSource": "passthrough"
      },
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.1,
      "position": [
        1184,
        32
      ],
      "id": "23cbe224-05e2-4c22-9baf-297642ee7ac6",
      "name": "When Executed by Another Workflow"
    },
    {
      "parameters": {
        "model": "openai/gpt-oss-120b",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGroq",
      "typeVersion": 1,
      "position": [
        1568,
        336
      ],
      "id": "2c4db3a9-c7b9-4199-9061-eaa251bec66b",
      "name": "ChatGPT OSS:120b",
      "credentials": {
        "groqApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Suggestion Request Preprocessor\nconst inputData = $input.all();\nconst requestData = inputData[0].json;\n\nconsole.log('=== Suggestion Generator - Request Preprocessor ===');\nconsole.log('Input:', requestData);\n\nconst query = requestData.query || requestData.message || '';\n\nif (!query || query.trim().length === 0) {\n  throw new Error('Query is required for suggestions');\n}\n\n// Extract ingredients if mentioned\nconst ingredientPatterns = [\n  /j'ai ([^.]+)/i,\n  /avec ([^.]+)/i,\n  /disposer? de ([^.]+)/i,\n  /ingr\u00e9dients?:?\\s*([^.]+)/i\n];\n\nlet extractedIngredients = [];\nfor (const pattern of ingredientPatterns) {\n  const match = query.match(pattern);\n  if (match) {\n    const ingredients = match[1]\n      .split(/,|et|;/)\n      .map(i => i.trim())\n      .filter(i => i.length > 2);\n    extractedIngredients.push(...ingredients);\n  }\n}\n\n// Prepare enhanced prompt\nconst enhancedPrompt = {\n  original_query: query,\n  extracted_ingredients: extractedIngredients,\n  prompt_type: extractedIngredients.length > 0 ? 'ingredient_based' : 'general',\n  timestamp: new Date().toISOString()\n};\n\nconsole.log('Preprocessed Request:', enhancedPrompt);\n\nreturn [enhancedPrompt];"
      },
      "id": "104bbaa4-f77d-47f5-bfd5-83f6342f65ee",
      "name": "Request Preprocessor",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1440,
        32
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.prompt_type === 'ingredient_based' ? \n'Sugg\u00e8re des plats camerounais utilisant: ' + $json.extracted_ingredients.join(', ') + '. Contexte: ' + $json.original_query :\n'Sugg\u00e8re des plats camerounais pour: ' + $json.original_query\n}}\n\nR\u00e9ponds UNIQUEMENT avec un JSON array de 5 suggestions.",
        "options": {
          "systemMessage": "Tu es TchopIA Suggestion Engine. G\u00e9n\u00e8re EXACTEMENT 5 suggestions de plats camerounais au format JSON strict:\n\n[\n  {\n    \"name\": \"Nom du plat\",\n    \"description\": \"Description 150-200 caract\u00e8res avec r\u00e9gion, ingr\u00e9dients cl\u00e9s, et profil gustatif\"\n  }\n]\n\nR\u00c8GLES:\n- EXACTEMENT 5 suggestions\n- JSON valide uniquement\n- Descriptions 150-200 caract\u00e8res\n- Varier r\u00e9gions et types de plats\n- Plats authentiques camerounais\n- Aucun texte en dehors du JSON"
        }
      },
      "id": "fa0b0a53-01f5-45e2-b1ac-02b96e75ec27",
      "name": "Suggestion AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 2.2,
      "position": [
        1664,
        16
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "jsCode": "// \ud83d\udd27 ENHANCED Suggestion Response Parser - Sub-Workflow\nconst inputData = $input.all();\nconst aiResponse = inputData[0].json;\n\nconsole.log('=== Enhanced Suggestion Parser - Sub-Workflow ===');\nconsole.log('Input Type:', typeof aiResponse);\n\nlet responseText = aiResponse.output || aiResponse.text || JSON.stringify(aiResponse);\nlet suggestions = [];\nlet parseMethod = 'unknown';\n\n// Method 1: Direct JSON array parsing - Multiple attempts\ntry {\n  // Try to find JSON arrays with different patterns\n  const patterns = [\n    /\\[\\s*\\{[\\s\\S]*?\"name\"[\\s\\S]*?\\}\\s*\\]/g,\n    /\\[\\s*\\{[\\s\\S]*?\"nom\"[\\s\\S]*?\\}\\s*\\]/g,\n    /\\[\\s*\\{[\\s\\S]*?\\}\\s*\\]/g\n  ];\n  \n  for (const pattern of patterns) {\n    const matches = responseText.match(pattern);\n    if (matches) {\n      for (const match of matches) {\n        try {\n          const parsed = JSON.parse(match);\n          if (Array.isArray(parsed) && parsed.length > 0 && parsed[0].name) {\n            suggestions = parsed;\n            parseMethod = 'json_array_pattern';\n            console.log('Parsed with pattern:', pattern);\n            break;\n          }\n        } catch (e) {}\n      }\n      if (suggestions.length > 0) break;\n    }\n  }\n} catch (e) {\n  console.log('Pattern matching failed:', e.message);\n}\n\n// Method 2: Extract from object containing suggestions\nif (suggestions.length === 0) {\n  try {\n    const objectMatch = responseText.match(/\\{[\\s\\S]*?\"suggestions\"\\s*:\\s*\\[[\\s\\S]*?\\][\\s\\S]*?\\}/);\n    if (objectMatch) {\n      const parsed = JSON.parse(objectMatch[0]);\n      if (parsed.suggestions && Array.isArray(parsed.suggestions)) {\n        suggestions = parsed.suggestions;\n        parseMethod = 'nested_suggestions';\n        console.log('Found nested suggestions object');\n      }\n    }\n  } catch (e) {\n    console.log('Nested suggestions parse failed:', e.message);\n  }\n}\n\n// Method 3: Multiple markdown code blocks\nif (suggestions.length === 0) {\n  try {\n    const codeBlocks = responseText.match(/```(?:json)?\\s*([\\s\\S]*?)```/g);\n    if (codeBlocks) {\n      for (const block of codeBlocks) {\n        const content = block.replace(/```(?:json)?\\s*|```/g, '').trim();\n        try {\n          const parsed = JSON.parse(content);\n          if (Array.isArray(parsed) && parsed[0] && parsed[0].name) {\n            suggestions = parsed;\n            parseMethod = 'markdown_block';\n            console.log('Parsed from markdown block');\n            break;\n          } else if (parsed.suggestions) {\n            suggestions = parsed.suggestions;\n            parseMethod = 'markdown_nested';\n            break;\n          }\n        } catch (e) {}\n      }\n    }\n  } catch (e) {\n    console.log('Markdown blocks parse failed:', e.message);\n  }\n}\n\n// Method 4: Enhanced table extraction\nif (suggestions.length === 0) {\n  const tablePatterns = [\n    /\\|\\s*(?:Plat|Nom|Name|Dish)[\\s\\S]*?\\|[\\s\\S]*?\\n([\\s\\S]*?)(?:\\n\\n|$)/,\n    /\\|[^|]*\\|[^|]*\\|[\\s\\S]*?\\n([\\s\\S]*?)(?:\\n\\n|$)/\n  ];\n  \n  for (const pattern of tablePatterns) {\n    const tableMatch = responseText.match(pattern);\n    if (tableMatch) {\n      const rows = tableMatch[1].split('\\n').filter(line => line.trim().startsWith('|'));\n      suggestions = rows.map(row => {\n        const cells = row.split('|').map(c => c.trim()).filter(c => c && c !== '---');\n        if (cells.length >= 2) {\n          return {\n            name: cells[0].replace(/\\*\\*|__|\"|'/g, '').trim(),\n            description: cells[1].replace(/\\*\\*|__|\"|'/g, '').trim()\n          };\n        }\n      }).filter(s => s && s.name && s.description && s.name.length > 2);\n      \n      if (suggestions.length > 0) {\n        parseMethod = 'table_extraction';\n        console.log('Extracted from table:', suggestions.length);\n        break;\n      }\n    }\n  }\n}\n\n// Method 5: Enhanced line-by-line extraction with multiple patterns\nif (suggestions.length === 0) {\n  const lines = responseText.split('\\n').filter(l => l.trim());\n  const patterns = [\n    /^\\d+\\.\\s*(?:\\*\\*)?([^*\\-:]+?)(?:\\*\\*)?\\s*[-:\u2013]\\s*(.+)$/,\n    /^\\*\\s*(?:\\*\\*)?([^*\\-:]+?)(?:\\*\\*)?\\s*[-:\u2013]\\s*(.+)$/,\n    /^-\\s*(?:\\*\\*)?([^*\\-:]+?)(?:\\*\\*)?\\s*[-:\u2013]\\s*(.+)$/,\n    /^(?:\\*\\*)?([A-Za-z\u00c0-\u00ff\\s]+?)(?:\\*\\*)?\\s*[:\u2013-]\\s*(.+)$/,\n    /^([A-Za-z\u00c0-\u00ff\\s]+?)\\s*[-\u2013:]\\s*(.{20,})$/\n  ];\n  \n  for (const line of lines) {\n    for (const pattern of patterns) {\n      const match = line.match(pattern);\n      if (match && match[1] && match[2]) {\n        const name = match[1].trim();\n        const description = match[2].trim();\n        if (name.length > 2 && description.length > 20) {\n          suggestions.push({\n            name: name,\n            description: description\n          });\n          break;\n        }\n      }\n    }\n  }\n  \n  if (suggestions.length > 0) {\n    parseMethod = 'line_extraction_enhanced';\n    console.log('Enhanced line extraction:', suggestions.length);\n  }\n}\n\n// Method 6: French cuisine-specific named entity extraction\nif (suggestions.length === 0) {\n  const cuisineTerms = [\n    'Ndol\u00e9', 'Eru', 'Koki', 'Fufu', 'Achu', 'Banga', 'Miondo', 'Kwacoco',\n    'Poulet DG', 'Poisson Brais\u00e9', 'Sauce Jaune', 'Gombo', 'Pistache',\n    'Mbongo', 'Sangah', 'Okok', 'Bitter Leaf', 'Water Fufu'\n  ];\n  \n  const dishPattern = new RegExp(`(${cuisineTerms.join('|')})`, 'gi');\n  const dishMatches = responseText.match(dishPattern);\n  \n  if (dishMatches) {\n    const uniqueDishes = [...new Set(dishMatches.map(d => d.toLowerCase()))];\n    suggestions = uniqueDishes.slice(0, 5).map(dish => ({\n      name: dish.charAt(0).toUpperCase() + dish.slice(1),\n      description: `Plat traditionnel camerounais ${dish} pr\u00e9par\u00e9 selon les m\u00e9thodes ancestrales avec des ingr\u00e9dients locaux authentiques.`\n    }));\n    parseMethod = 'cuisine_entity_extraction';\n    console.log('Extracted cuisine entities:', suggestions.length);\n  }\n}\n\n// Enhanced fallback with diverse regional suggestions\nif (suggestions.length === 0) {\n  suggestions = [\n    {\n      name: \"Ndol\u00e9\",\n      description: \"Plat national du Cameroun originaire du Centre, aux feuilles d'ait\u00e9 marin\u00e9es dans une sauce cr\u00e9meuse d'arachides grill\u00e9es, enrichie de viande et crevettes fum\u00e9es - Un symbole de l'hospitalit\u00e9 camerounaise\",\n      region: \"Centre\",\n      category: \"Plat principal\"\n    },\n    {\n      name: \"Eru\",\n      description: \"Sp\u00e9cialit\u00e9 embl\u00e9matique du Sud-Ouest aux feuilles d'eru finement cisel\u00e9es, m\u00e9lang\u00e9es au water fufu onctueux, viande et crayfish pour une texture filante unique et savoureuse\",\n      region: \"Sud-Ouest\",\n      category: \"Plat principal\"\n    },\n    {\n      name: \"Koki\",\n      description: \"G\u00e2teau traditionnel de l'Ouest fait de haricots blancs moulus cuits \u00e0 la vapeur dans des feuilles de bananier, parfum\u00e9 aux \u00e9pices et huile de palme rouge authentique\",\n      region: \"Ouest\",\n      category: \"Plat v\u00e9g\u00e9tarien\"\n    },\n    {\n      name: \"Achu Soup\",\n      description: \"Soupe jaune onctueuse du Nord-Ouest pr\u00e9par\u00e9e avec limestone et huile de palme, traditionnellement accompagn\u00e9e de boulettes de coco-yam pil\u00e9es au mortier\",\n      region: \"Nord-Ouest\",\n      category: \"Soupe\"\n    },\n    {\n      name: \"Poisson Brais\u00e9\",\n      description: \"Sp\u00e9cialit\u00e9 c\u00f4ti\u00e8re o\u00f9 le poisson frais est marin\u00e9 aux \u00e9pices locales puis grill\u00e9 sur braises, servi avec plantains et sauce tomate \u00e9pic\u00e9e - D\u00e9lice des r\u00e9gions littorales\",\n      region: \"Littoral\",\n      category: \"Grillades\"\n    }\n  ];\n  parseMethod = 'enhanced_regional_fallback';\n  console.log('Using enhanced regional fallback suggestions');\n}\n\n// Enhanced validation and enrichment\nsuggestions = suggestions\n  .filter(s => s && s.name && s.description)\n  .filter(s => s.name.trim().length > 1 && s.description.trim().length > 20)\n  .slice(0, 5)\n  .map((s, index) => ({\n    id: `suggestion_${Date.now()}_${index}`,\n    name: s.name.replace(/[*_#`\"']/g, '').trim(),\n    description: s.description.replace(/[*_`\"']/g, '').trim(),\n    region: s.region || 'Cameroun',\n    category: s.category || 'Cuisine Camerounaise',\n    estimated_prep_time: s.prep_time || '30-60 min',\n    difficulty: s.difficulty || 'Moyen',\n    authenticity: 'Traditional'\n  }));\n\n// Ensure minimum quality descriptions\nsuggestions = suggestions.map(s => {\n  if (s.description.length < 50) {\n    s.description += ` - Recette authentique de la cuisine camerounaise, riche en saveurs et traditions culinaires.`;\n  }\n  return s;\n});\n\nconsole.log('Final suggestions count:', suggestions.length);\nconsole.log('Parse method:', parseMethod);\nconsole.log('First suggestion:', suggestions[0] ? suggestions[0].name : 'None');\n\nreturn [{\n  success: true,\n  action: 'get_suggestions',\n  data_type: 'suggestions',\n  suggestions: suggestions,\n  count: suggestions.length,\n  parse_method: parseMethod,\n  timestamp: new Date().toISOString(),\n  metadata: {\n    source: 'sub_workflow_parser',\n    quality: parseMethod.includes('fallback') ? 'default' : 'ai_generated',\n    parser_version: '2.0_enhanced',\n    has_regional_info: suggestions.some(s => s.region && s.region !== 'Cameroun')\n  }\n}];"
      },
      "id": "7990c0f1-c63a-4da8-b2de-3cc413221ddb",
      "name": "Response Parser",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2128,
        -32
      ]
    },
    {
      "parameters": {
        "jsCode": "// Error handler\nreturn [{\n  success: false,\n  action: 'get_suggestions',\n  error: 'generation_failed',\n  message: 'Impossible de g\u00e9n\u00e9rer des suggestions',\n  suggestions: [\n    {name: \"Ndol\u00e9\", description: \"Plat national camerounais aux arachides et feuilles\"},\n    {name: \"Eru\", description: \"Sp\u00e9cialit\u00e9 du Sud-Ouest aux feuilles et water fufu\"}\n  ],\n  timestamp: new Date().toISOString()\n}];"
      },
      "id": "3b1682f1-9d08-40ff-893b-fd2416e55960",
      "name": "Error Handler",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2128,
        160
      ]
    }
  ],
  "connections": {
    "When Executed by Another Workflow": {
      "main": [
        [
          {
            "node": "Request Preprocessor",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ChatGPT OSS:120b": {
      "ai_languageModel": [
        [
          {
            "node": "Suggestion AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Request Preprocessor": {
      "main": [
        [
          {
            "node": "Suggestion AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Suggestion AI Agent": {
      "main": [
        [
          {
            "node": "Response Parser",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error Handler",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "734d838f-b687-4ecd-b3a3-61006ef50ce2",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "C60bVIonekWy8VAE",
  "tags": []
}

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.

About this workflow

suggestion_generator. Uses executeWorkflowTrigger, lmChatGroq, agent. Event-driven trigger; 6 nodes.

Source: https://github.com/Worketyamo-Students/Danielle_site1_Bootcamp/blob/ae12fcdd0a854493d32954771d0ce7d94e5590b8/n8n-workflows/suggestion_generator.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →