This workflow follows the Agent → Groq Chat 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 →
{
"name": "tchopia-ai-workflow",
"nodes": [
{
"parameters": {
"jsCode": "// Request Validator and Preprocessor\nconst inputData = $input.all();\nconst requestBody = inputData[0].json.body || {};\n\nconsole.log('=== TchopIA Request Preprocessor ===');\nconsole.log('Raw Request:', JSON.stringify(requestBody, null, 2));\n\n// Extract request parameters\nconst query = requestBody.query || requestBody.message || '';\nconst explicitAction = requestBody.action || null;\nconst context = requestBody.context || {};\n\n// Validate request\nif (!query || query.trim().length === 0) {\n throw new Error('Query cannot be empty');\n}\n\n// Prepare data for AI classification\nconst preprocessedData = {\n query: query,\n explicit_action: explicitAction,\n has_explicit_action: !!explicitAction,\n context: context,\n timestamp: new Date().toISOString(),\n session_id: inputData[0].json.headers?.['x-session-id'] || `session_${Date.now()}`,\n user_agent: inputData[0].json.headers?.['user-agent'] || 'unknown'\n};\n\nconsole.log('Preprocessed Data:', preprocessedData);\n\nreturn [preprocessedData];"
},
"id": "019b3402-de5a-459f-a207-a9e2bff7c92e",
"name": "Request Preprocessor",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-3904,
-592
]
},
{
"parameters": {
"promptType": "define",
"text": "={{ $json.query }}",
"options": {
"systemMessage": "=Tu es un classificateur d'intentions pour TchopIA, assistant culinaire camerounais.\n\nTa mission: Analyser la requ\u00eate utilisateur et d\u00e9terminer l'action appropri\u00e9e.\n\n\ud83c\udfaf ACTIONS POSSIBLES:\n1. **get_suggestions** - L'utilisateur cherche des id\u00e9es de plats, des suggestions bas\u00e9es sur ingr\u00e9dients disponibles, ou ne sait pas quoi cuisiner\n2. **generate_recipe** - L'utilisateur demande explicitement une recette compl\u00e8te, les \u00e9tapes de pr\u00e9paration d'un plat sp\u00e9cifique\n3. **cooking_advice** - L'utilisateur demande des conseils, astuces, techniques, ou aide pour r\u00e9soudre un probl\u00e8me culinaire\n\n\ud83d\udccb EXEMPLES DE CLASSIFICATION:\n\n**get_suggestions**:\n- \"J'ai du poulet, des poivrons et des oignons. Que puis-je cuisiner?\"\n- \"Sugg\u00e8re-moi des plats camerounais pour ce soir\"\n- \"Quels plats typiques du Cameroun puis-je faire?\"\n- \"Je veux d\u00e9couvrir la cuisine camerounaise\"\n\n**generate_recipe**:\n- \"Comment pr\u00e9parer le Ndol\u00e9?\"\n- \"Donne-moi la recette compl\u00e8te du Poulet DG\"\n- \"Quelles sont les \u00e9tapes pour faire l'Eru?\"\n- \"Je veux cuisiner du Koki, comment faire?\"\n\n**cooking_advice**:\n- \"Comment rendre mes beignets plus moelleux?\"\n- \"Pourquoi mon Ndol\u00e9 est trop amer?\"\n- \"Quelle est la meilleure technique pour griller le poisson?\"\n- \"Comment conserver le plantain m\u00fbr?\"\n\n\u26a1 R\u00c8GLES DE CLASSIFICATION:\n1. Si action explicite fournie ({{ $json.has_explicit_action ? 'OUI: ' + $json.explicit_action : 'NON' }}), utilise-la\n2. Sinon, analyse le contexte et l'intention\n3. Par d\u00e9faut en cas de doute: get_suggestions\n4. Sois confiant dans ta d\u00e9cision\n\n\ud83d\udce4 R\u00c9PONSE ATTENDUE (JSON STRICT):\nR\u00e9ponds UNIQUEMENT avec ce format JSON, rien d'autre:\n{\n \"action\": \"get_suggestions\" | \"generate_recipe\" | \"cooking_advice\",\n \"confidence\": \"high\" | \"medium\" | \"low\",\n \"reasoning\": \"Courte explication (1 phrase)\"\n}\n\nRequ\u00eate utilisateur: {{ $json.query }}"
}
},
"id": "4ecca1cf-1317-4244-81c6-021e7c0f0692",
"name": "Intent Classifier AI",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 2.2,
"position": [
-3664,
-592
],
"onError": "continueErrorOutput"
},
{
"parameters": {
"jsCode": "// Classification Result Parser\nconst inputData = $input.all();\nconst classifierResponse = inputData[0].json;\n\nconsole.log('=== Intent Classification Result ===');\nconsole.log('Raw Response:', JSON.stringify(classifierResponse, null, 2));\n\n// Extract classification from AI response\nlet classification = {\n action: 'get_suggestions',\n confidence: 'low',\n reasoning: 'Default fallback'\n};\n\nconst responseText = classifierResponse.output || classifierResponse.text || JSON.stringify(classifierResponse);\n\n// Try to parse JSON response\ntry {\n // Look for JSON object in response\n const jsonMatch = responseText.match(/\\{[\\s\\S]*?\"action\"[\\s\\S]*?\\}/);\n if (jsonMatch) {\n const parsed = JSON.parse(jsonMatch[0]);\n if (parsed.action && ['get_suggestions', 'generate_recipe', 'cooking_advice'].includes(parsed.action)) {\n classification = parsed;\n }\n }\n} catch (e) {\n console.error('JSON parsing failed:', e);\n}\n\n// Validate and use explicit action if available\nconst preprocessorData = $('Request Preprocessor').item.json;\nif (preprocessorData.has_explicit_action && preprocessorData.explicit_action) {\n classification.action = preprocessorData.explicit_action;\n classification.confidence = 'high';\n classification.reasoning = 'Action explicitement fournie par l\\'utilisateur';\n}\n\n// Prepare final routing data\nconst routingData = {\n action: classification.action,\n query: preprocessorData.query,\n confidence: classification.confidence,\n reasoning: classification.reasoning,\n classification_method: preprocessorData.has_explicit_action ? 'explicit' : 'ai_classified',\n explicit_action: preprocessorData.has_explicit_action,\n context: preprocessorData.context,\n timestamp: preprocessorData.timestamp,\n session_id: preprocessorData.session_id,\n user_agent: preprocessorData.user_agent\n};\n\nconsole.log('Final Routing Decision:', routingData);\n\nreturn [routingData];"
},
"id": "f3173050-0e21-434e-86fd-0c4bc8230fe8",
"name": "Classification Parser",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-3296,
-608
]
},
{
"parameters": {
"sessionIdType": "customKey",
"sessionKey": "={{ $('Classification Parser').item.json.session_id }}",
"contextWindowLength": 10
},
"id": "027c1a84-d1ac-41ca-bb02-2eb98a3b7dc2",
"name": "Conversation Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"typeVersion": 1.3,
"position": [
-3056,
-384
]
},
{
"parameters": {
"jsCode": "// Error Handler\nconst error = $input.all()[0].json;\n\nconsole.error('=== TchopIA Error ===');\nconsole.error('Error:', error);\n\nconst errorResponse = {\n success: false,\n error: 'request_failed',\n message: 'D\u00e9sol\u00e9, une erreur s\\'est produite. Veuillez r\u00e9essayer.',\n suggestions: [\n 'Reformulez votre demande plus simplement',\n 'Assurez-vous que votre requ\u00eate est claire',\n 'R\u00e9essayez dans quelques instants'\n ],\n support: {\n error_id: `error_${Date.now()}`,\n timestamp: new Date().toISOString()\n },\n metadata: {\n source: 'TchopIA AI Assistant',\n version: '2.0'\n }\n};\n\nreturn [errorResponse];"
},
"id": "380f4441-2be2-42d4-9562-221f9cc58161",
"name": "Error Handler",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1968,
-112
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {
"responseHeaders": {
"entries": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "X-TchopIA-Version",
"value": "2.0"
}
]
}
}
},
"id": "3b53561a-6700-4534-bbd8-609ecce0fcb4",
"name": "Webhook Response1",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
-1312,
-416
]
},
{
"parameters": {
"httpMethod": "POST",
"path": "tchopia-ai",
"responseMode": "responseNode",
"options": {
"allowedOrigins": "*"
}
},
"id": "ec1d2c2b-3803-42b5-9ef3-a4b9e69ea7fb",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
-4144,
-592
]
},
{
"parameters": {
"model": "openai/gpt-oss-120b",
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatGroq",
"typeVersion": 1,
"position": [
-3600,
16
],
"id": "b4f9c719-ec11-434a-b3d2-7c63700c7199",
"name": "Groq Chat Model",
"credentials": {
"groqApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"leftValue": "={{ $('Classification Parser').item.json.action }}",
"rightValue": "get_suggestions",
"operator": {
"type": "string",
"operation": "equals"
},
"id": "f26d2793-4662-49ac-92ba-5fa56501dc8f"
}
],
"combinator": "and"
}
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "194f05f9-df29-42f0-8f0d-f885f7549bd3",
"leftValue": "={{ $('Classification Parser').item.json.action }}",
"rightValue": "cooking_advice",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
}
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "5710f3ca-b065-493a-8c6f-40f6f3a74fea",
"leftValue": "={{ $('Classification Parser').item.json.action }}",
"rightValue": "generate_recipe",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
}
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.2,
"position": [
-2384,
-560
],
"id": "33302808-57c0-4cfc-bb40-15804485a13c",
"name": "Switch"
},
{
"parameters": {
"jsCode": "// \ud83d\udd27 ENHANCED Suggestions Response Parser\nconst inputData = $input.all();\nconst aiResponse = inputData[0].json;\n\nconsole.log('=== Suggestions Parser - Main Workflow ===');\nconsole.log('Input Type:', typeof aiResponse);\n\nlet responseText = '';\nlet suggestions = [];\nlet parseMethod = 'unknown';\nlet rawData = aiResponse;\n\n// Enhanced data extraction from various response formats\nif (aiResponse.data && typeof aiResponse.data === 'object') {\n // Tool workflow response format\n rawData = aiResponse.data;\n console.log('Extracted data from tool response');\n}\n\nif (rawData.output) responseText = rawData.output;\nelse if (rawData.text) responseText = rawData.text;\nelse if (rawData.suggestions) {\n // Direct suggestions object from sub-workflow\n suggestions = Array.isArray(rawData.suggestions) ? rawData.suggestions : [];\n parseMethod = 'direct_object';\n console.log('Found direct suggestions array:', suggestions.length);\n}\nelse responseText = JSON.stringify(rawData);\n\n// Method 1: Direct JSON array parsing\nif (suggestions.length === 0 && responseText) {\n try {\n const jsonArrayMatch = responseText.match(/\\[\\s*\\{[\\s\\S]*?\"name\"[\\s\\S]*?\\}\\s*\\]/g);\n if (jsonArrayMatch) {\n const parsed = JSON.parse(jsonArrayMatch[jsonArrayMatch.length - 1]);\n if (Array.isArray(parsed) && parsed.length > 0) {\n suggestions = parsed;\n parseMethod = 'json_array';\n console.log('Parsed JSON array:', suggestions.length);\n }\n }\n } catch (e) {\n console.log('JSON array parse failed:', e.message);\n }\n}\n\n// Method 2: Nested suggestions object\nif (suggestions.length === 0 && responseText) {\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_object';\n console.log('Found nested suggestions:', suggestions.length);\n }\n }\n } catch (e) {\n console.log('Nested object parse failed:', e.message);\n }\n}\n\n// Method 3: Markdown code block\nif (suggestions.length === 0 && responseText) {\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 const parsed = JSON.parse(content);\n if (Array.isArray(parsed)) {\n suggestions = parsed;\n parseMethod = 'markdown_array';\n break;\n } else if (parsed.suggestions) {\n suggestions = parsed.suggestions;\n parseMethod = 'markdown_object';\n break;\n }\n }\n console.log('Parsed from markdown:', suggestions.length);\n }\n } catch (e) {\n console.log('Markdown parse failed:', e.message);\n }\n}\n\n// Method 4: Markdown table extraction\nif (suggestions.length === 0 && responseText) {\n const tableMatch = responseText.match(/\\|\\s*(?:Plat|Nom|Name)[\\s\\S]*?\\|[\\s\\S]*?\\n([\\s\\S]*?)(?:\\n\\n|$)/);\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);\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);\n \n if (suggestions.length > 0) {\n parseMethod = 'markdown_table';\n console.log('Extracted from table:', suggestions.length);\n }\n }\n}\n\n// Method 5: Line-by-line numbered list extraction\nif (suggestions.length === 0 && responseText) {\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 ];\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 suggestions.push({\n name: match[1].trim(),\n description: match[2].trim()\n });\n break;\n }\n }\n }\n \n if (suggestions.length > 0) {\n parseMethod = 'line_extraction';\n console.log('Extracted from lines:', suggestions.length);\n }\n}\n\n// Fallback: High-quality default suggestions\nif (suggestions.length === 0) {\n suggestions = [\n {name: \"Ndol\u00e9\", description: \"Plat national du Cameroun avec feuilles d'ait\u00e9 marin\u00e9es dans une sauce cr\u00e9meuse d'arachides grill\u00e9es, enrichie de viande et crevettes fum\u00e9es - Un d\u00e9lice de la r\u00e9gion du Centre\"},\n {name: \"Eru\", description: \"Sp\u00e9cialit\u00e9 traditionnelle du Sud-Ouest aux feuilles d'eru finement cisel\u00e9es, m\u00e9lang\u00e9es au water fufu, viande et crayfish pour une texture filante unique\"},\n {name: \"Poulet DG (Directeur G\u00e9n\u00e9ral)\", description: \"Version moderne et festive de poulet saut\u00e9 aux l\u00e9gumes color\u00e9s, plantains dor\u00e9s et \u00e9pices, devenu incontournable des grandes c\u00e9l\u00e9brations\"},\n {name: \"Koki\", description: \"G\u00e2teau de haricots blancs moulus \u00e0 la vapeur dans des feuilles de bananier, sp\u00e9cialit\u00e9 de l'Ouest parfum\u00e9e aux \u00e9pices et huile de palme\"},\n {name: \"Achu Soup\", description: \"Soupe jaune onctueuse du Nord-Ouest \u00e0 base de limestone et huile de palme, accompagn\u00e9e de boulettes de coco-yam pil\u00e9es\"}\n ];\n parseMethod = 'fallback';\n console.log('Using enhanced fallback suggestions');\n}\n\n// Enhanced validation and cleaning\nsuggestions = suggestions\n .filter(s => s && s.name && s.description)\n .filter(s => s.name.trim().length > 0 && s.description.trim().length > 10)\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 category: s.category || 'Cuisine Camerounaise',\n region: s.region || null\n }));\n\nconsole.log('Final suggestions count:', suggestions.length);\nconsole.log('Parse method:', parseMethod);\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: 'main_workflow_parser',\n quality: parseMethod === 'fallback' ? 'default' : 'ai_generated'\n }\n}];"
},
"id": "55a2b6c1-7b8d-4032-920e-2d95233dcdf6",
"name": "Gets suggestions Parser",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1968,
-704
]
},
{
"parameters": {
"jsCode": "// \ud83d\udd27 ENHANCED Cooking Advice Response Parser\nconst inputData = $input.all();\nconst aiResponse = inputData[0].json;\n\nconsole.log('=== Advice Parser - Main Workflow ===');\nconsole.log('Input Type:', typeof aiResponse);\n\nlet responseText = '';\nlet advice = null;\nlet parseMethod = 'unknown';\nlet rawData = aiResponse;\n\n// Enhanced data extraction from various response formats\nif (aiResponse.data && typeof aiResponse.data === 'object') {\n rawData = aiResponse.data;\n console.log('Extracted data from tool response');\n}\n\nif (rawData.output) responseText = rawData.output;\nelse if (rawData.text) responseText = rawData.text;\nelse if (rawData.advice && typeof rawData.advice === 'object') {\n advice = rawData.advice;\n parseMethod = 'direct_object';\n console.log('Found direct advice object');\n}\nelse responseText = JSON.stringify(rawData);\n\n// Method 1: Direct JSON object with advice_type\nif (!advice && responseText) {\n try {\n const jsonMatch = responseText.match(/\\{[\\s\\S]*?\"advice_type\"[\\s\\S]*?\\}/);\n if (jsonMatch) {\n const parsed = JSON.parse(jsonMatch[0]);\n if (parsed.advice_type || parsed.main_advice) {\n advice = parsed;\n parseMethod = 'direct_json';\n console.log('Parsed advice JSON');\n }\n }\n } catch (e) {\n console.log('Direct JSON parse failed:', e.message);\n }\n}\n\n// Method 2: Nested advice object\nif (!advice && responseText) {\n try {\n const objectMatch = responseText.match(/\\{[\\s\\S]*?\"advice\"\\s*:\\s*\\{[\\s\\S]*?\\}[\\s\\S]*?\\}/);\n if (objectMatch) {\n const parsed = JSON.parse(objectMatch[0]);\n if (parsed.advice && typeof parsed.advice === 'object') {\n advice = parsed.advice;\n parseMethod = 'nested_object';\n console.log('Found nested advice object');\n }\n }\n } catch (e) {\n console.log('Nested object parse failed:', e.message);\n }\n}\n\n// Method 3: Markdown code blocks\nif (!advice && responseText) {\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 const parsed = JSON.parse(content);\n if (parsed.advice_type || parsed.main_advice) {\n advice = parsed;\n parseMethod = 'markdown_json';\n console.log('Parsed from markdown block');\n break;\n } else if (parsed.advice) {\n advice = parsed.advice;\n parseMethod = 'markdown_nested';\n break;\n }\n }\n }\n } catch (e) {\n console.log('Markdown parse failed:', e.message);\n }\n}\n\n// Method 4: Structured text parsing\nif (!advice && responseText) {\n const structuredAdvice = {\n advice_type: 'G\u00e9n\u00e9ral',\n main_advice: '',\n quick_tips: [],\n traditional_secrets: [],\n ingredients_focus: {\n recommended: [],\n substitutes: [],\n avoid: []\n },\n step_by_step: [],\n cultural_context: '',\n common_mistakes: [],\n seasonal_notes: '',\n difficulty_level: 'Interm\u00e9diaire',\n estimated_time: 'Variable'\n };\n\n // Extract main advice\n const sections = responseText.split(/\\n\\n+/);\n if (sections.length > 0) {\n structuredAdvice.main_advice = sections[0].substring(0, 800);\n }\n\n // Extract tips\n const tipsMatch = responseText.match(/(?:Astuce|Conseil|Tips?)\\s*:?\\s*\\n?([\\s\\S]*?)(?:\\n\\n|$)/i);\n if (tipsMatch) {\n structuredAdvice.quick_tips = tipsMatch[1]\n .split(/\\n/)\n .filter(line => line.trim().match(/^[-*\u2022]|^\\d+\\./)) \n .map(line => line.replace(/^[-*\u2022]|^\\d+\\./, '').trim())\n .filter(tip => tip.length > 10)\n .slice(0, 5);\n }\n\n // Extract secrets\n const secretsMatch = responseText.match(/(?:Secret|Tradition)\\s*:?\\s*\\n?([\\s\\S]*?)(?:\\n\\n|$)/i);\n if (secretsMatch) {\n structuredAdvice.traditional_secrets = secretsMatch[1]\n .split(/\\n/)\n .filter(line => line.trim().match(/^[-*\u2022]|^\\d+\\./)) \n .map(line => line.replace(/^[-*\u2022]|^\\d+\\./, '').trim())\n .filter(s => s.length > 10)\n .slice(0, 3);\n }\n\n if (structuredAdvice.main_advice || structuredAdvice.quick_tips.length > 0) {\n advice = structuredAdvice;\n parseMethod = 'text_extraction';\n console.log('Extracted from structured text');\n }\n}\n\n// Enhanced fallback with comprehensive default advice\nif (!advice) {\n const query = rawData.query || 'question culinaire';\n advice = {\n advice_type: 'G\u00e9n\u00e9ral',\n main_advice: `Pour r\u00e9ussir vos plats camerounais, voici quelques conseils essentiels bas\u00e9s sur les traditions culinaires de nos r\u00e9gions. L'utilisation d'ingr\u00e9dients frais et locaux est primordiale pour obtenir des saveurs authentiques. Les \u00e9pices doivent \u00eatre bien dos\u00e9es pour respecter l'\u00e9quilibre des go\u00fbts. La patience durant la cuisson permet aux saveurs de bien se d\u00e9velopper. N'h\u00e9sitez pas \u00e0 adapter les recettes selon les ingr\u00e9dients disponibles tout en gardant l'esprit de la tradition.`,\n quick_tips: [\n \"Utiliser des ingr\u00e9dients frais et de saison pour une meilleure qualit\u00e9\",\n \"Respecter les temps de cuisson traditionnels pour d\u00e9velopper les saveurs\",\n \"Doser progressivement les \u00e9pices et go\u00fbter r\u00e9guli\u00e8rement\",\n \"Pr\u00e9parer certains ingr\u00e9dients la veille pour gagner du temps\",\n \"Conserver les restes au r\u00e9frig\u00e9rateur dans des contenants herm\u00e9tiques\"\n ],\n traditional_secrets: [\n \"Le secret des grands-m\u00e8res: laisser mijoter \u00e0 feu doux pour concentrer les ar\u00f4mes\",\n \"Moudre les \u00e9pices fra\u00eeches juste avant utilisation pour plus de parfum\",\n \"Utiliser l'huile de palme authentique pour le go\u00fbt traditionnel\"\n ],\n ingredients_focus: {\n recommended: [\"\u00c9pices locales fra\u00eeches\", \"L\u00e9gumes de saison\", \"Huile de palme rouge\", \"Piment frais\"],\n substitutes: [\"Beurre de cacahu\u00e8te pour l'huile d'arachide\", \"\u00c9pinards pour certaines feuilles traditionnelles\"],\n avoid: [\"Ingr\u00e9dients transform\u00e9s industriellement\", \"\u00c9pices p\u00e9rim\u00e9es\"]\n },\n step_by_step: [\n {step: 1, action: \"Pr\u00e9parer et laver tous les ingr\u00e9dients\", tip: \"Organiser son espace de travail facilite la cuisine\"},\n {step: 2, action: \"Respecter l'ordre d'incorporation des ingr\u00e9dients\", tip: \"Certains ingr\u00e9dients n\u00e9cessitent plus de temps de cuisson\"},\n {step: 3, action: \"Go\u00fbter et ajuster l'assaisonnement progressivement\", tip: \"Il est plus facile d'ajouter que de retirer des \u00e9pices\"}\n ],\n cultural_context: \"La cuisine camerounaise est riche et diversifi\u00e9e, refl\u00e9tant les traditions de plus de 250 ethnies. Chaque r\u00e9gion a ses sp\u00e9cialit\u00e9s et techniques transmises de g\u00e9n\u00e9ration en g\u00e9n\u00e9ration.\",\n common_mistakes: [\n \"Ne pas laisser mijoter assez longtemps - Solution: Respecter les temps de cuisson indiqu\u00e9s\",\n \"Mettre trop d'\u00e9pices d'un coup - Solution: Ajouter progressivement en go\u00fbtant\",\n \"Utiliser des ingr\u00e9dients de mauvaise qualit\u00e9 - Solution: Privil\u00e9gier le frais et local\"\n ],\n seasonal_notes: \"Adapter les recettes selon la disponibilit\u00e9 des ingr\u00e9dients de saison pour un meilleur r\u00e9sultat et respect de la tradition\",\n difficulty_level: \"Interm\u00e9diaire\",\n estimated_time: \"Variable selon le plat (30 min \u00e0 2h)\"\n };\n parseMethod = 'enhanced_fallback';\n console.log('Using enhanced fallback advice');\n}\n\n// Validation and enhancement\nif (advice) {\n // Ensure all required fields\n advice.advice_type = advice.advice_type || 'G\u00e9n\u00e9ral';\n advice.main_advice = advice.main_advice || advice.main || 'Conseil culinaire traditionnel camerounais';\n advice.quick_tips = Array.isArray(advice.quick_tips) ? advice.quick_tips : [];\n advice.traditional_secrets = Array.isArray(advice.traditional_secrets) ? advice.traditional_secrets : [];\n advice.common_mistakes = Array.isArray(advice.common_mistakes) ? advice.common_mistakes : [];\n \n // Ensure ingredients_focus structure\n if (!advice.ingredients_focus || typeof advice.ingredients_focus !== 'object') {\n advice.ingredients_focus = {\n recommended: [],\n substitutes: [],\n avoid: []\n };\n }\n \n // Add metadata\n advice.generated_at = new Date().toISOString();\n advice.language = 'fr';\n}\n\nconsole.log('Final advice parsed successfully');\nconsole.log('Parse method:', parseMethod);\n\nreturn [{\n success: true,\n action: 'cooking_advice',\n data_type: 'advice',\n advice: advice,\n parse_method: parseMethod,\n timestamp: new Date().toISOString(),\n metadata: {\n source: 'main_workflow_parser',\n quality: parseMethod.includes('fallback') ? 'default' : 'ai_generated',\n has_structured_data: !!(advice.step_by_step && advice.step_by_step.length > 0)\n }\n}];"
},
"id": "14eae05e-067a-4e51-b2b0-dc79e0f51fd6",
"name": "Cooking Advice Parser",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1968,
-496
]
},
{
"parameters": {
"description": "Appel cet outil quand l'utilisateur demande des suggestions de plats camerounais, des id\u00e9es de recettes, ou quand il mentionne des ingr\u00e9dients disponibles. Transmet TOUTE la requ\u00eate de l'utilisateur telle quelle.",
"workflowId": {
"__rl": true,
"value": "C60bVIonekWy8VAE",
"mode": "list",
"cachedResultName": "suggestion_generator"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"query": "={{ $json.query }}"
}
}
},
"id": "c0c33c1f-e9a1-4c60-9cec-9321ace8d1ec",
"name": "Suggestion Agent",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.2,
"position": [
-3184,
176
]
},
{
"parameters": {
"description": "Appel cet outil quand l'utilisateur demande explicitement une recette compl\u00e8te, des instructions de pr\u00e9paration d\u00e9taill\u00e9es, ou comment cuisiner un plat sp\u00e9cifique. Transmet le nom du plat et tout contexte pertinent.",
"workflowId": {
"__rl": true,
"value": "coNDPEfojluaHvhC",
"mode": "list",
"cachedResultName": "recipe_generator"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"query": "={{ $json.query }}",
"recipe_name": "={{ $json.context.recipe_name || '' }}",
"recipe_description": "={{ $json.context.description || '' }}"
}
}
},
"id": "1e5e4205-0593-4c61-a5e9-7565b0c12253",
"name": "Recipe Agent",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.2,
"position": [
-3008,
176
]
},
{
"parameters": {
"description": "Appel cet outil quand l'utilisateur demande des conseils culinaires, des astuces de cuisine, des techniques de pr\u00e9paration, ou des solutions \u00e0 des probl\u00e8mes culinaires. Transmet toute la question ou probl\u00e9matique.",
"workflowId": {
"__rl": true,
"value": "uMFHRt7rGUhqHNwU",
"mode": "list",
"cachedResultName": "advice_generator"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"query": "={{ $json.query }}"
}
}
},
"id": "ac19d57c-0263-4c22-8e97-28a8e2972a90",
"name": "Advice Agent",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.2,
"position": [
-2832,
176
]
},
{
"parameters": {
"jsCode": "// \ud83d\udd27 ENHANCED Recipe Response Parser\nconst inputData = $input.all();\nconst aiResponse = inputData[0].json;\n\nconsole.log('=== Recipe Parser - Main Workflow ===');\nconsole.log('Input Type:', typeof aiResponse);\n\nlet responseText = '';\nlet recipe = null;\nlet parseMethod = 'unknown';\nlet rawData = aiResponse;\n\n// Enhanced data extraction from various response formats\nif (aiResponse.data && typeof aiResponse.data === 'object') {\n rawData = aiResponse.data;\n console.log('Extracted data from tool response');\n}\n\nif (rawData.output) responseText = rawData.output;\nelse if (rawData.text) responseText = rawData.text;\nelse if (rawData.recipe && typeof rawData.recipe === 'object') {\n recipe = rawData.recipe;\n parseMethod = 'direct_object';\n console.log('Found direct recipe object');\n}\nelse responseText = JSON.stringify(rawData);\n\n// Method 1: Direct JSON object with recipe structure\nif (!recipe && responseText) {\n try {\n const jsonMatch = responseText.match(/\\{[\\s\\S]*?\"name\"[\\s\\S]*?\"ingredients\"[\\s\\S]*?\\}/);\n if (jsonMatch) {\n const parsed = JSON.parse(jsonMatch[0]);\n if (parsed.name && (parsed.ingredients || parsed.instructions)) {\n recipe = parsed;\n parseMethod = 'direct_json';\n console.log('Parsed recipe JSON');\n }\n }\n } catch (e) {\n console.log('Direct JSON parse failed:', e.message);\n }\n}\n\n// Method 2: Nested recipe object\nif (!recipe && responseText) {\n try {\n const objectMatch = responseText.match(/\\{[\\s\\S]*?\"recipe\"\\s*:\\s*\\{[\\s\\S]*?\\}[\\s\\S]*?\\}/);\n if (objectMatch) {\n const parsed = JSON.parse(objectMatch[0]);\n if (parsed.recipe && typeof parsed.recipe === 'object') {\n recipe = parsed.recipe;\n parseMethod = 'nested_object';\n console.log('Found nested recipe object');\n }\n }\n } catch (e) {\n console.log('Nested object parse failed:', e.message);\n }\n}\n\n// Method 3: Markdown code blocks\nif (!recipe && responseText) {\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 const parsed = JSON.parse(content);\n if (parsed.name && (parsed.ingredients || parsed.instructions)) {\n recipe = parsed;\n parseMethod = 'markdown_json';\n console.log('Parsed from markdown block');\n break;\n } else if (parsed.recipe) {\n recipe = parsed.recipe;\n parseMethod = 'markdown_nested';\n break;\n }\n }\n }\n } catch (e) {\n console.log('Markdown parse failed:', e.message);\n }\n}\n\n// Enhanced fallback with comprehensive default recipe\nif (!recipe) {\n const dishName = rawData.query || rawData.dish_name || 'Plat Camerounais';\n recipe = {\n name: dishName,\n description: \"Recette traditionnelle camerounaise authentique combinant ingr\u00e9dients locaux et techniques transmises de g\u00e9n\u00e9ration en g\u00e9n\u00e9ration.\",\n region: 'Cameroun',\n difficulty: 'Moyen',\n prep_time: 30,\n cook_time: 60,\n servings: '4-6 personnes',\n ingredients: [\n {item: \"Ingr\u00e9dients traditionnels\", quantity: \"Selon recette\", notes: \"Utiliser produits frais et locaux\"},\n {item: \"\u00c9pices camerounaises\", quantity: \"Au go\u00fbt\", notes: \"Doser progressivement\"}\n ],\n instructions: [\n {step: 1, action: \"Pr\u00e9parer et nettoyer tous les ingr\u00e9dients\", time: \"15 min\", tips: \"Organisation facilite la cuisson\"},\n {step: 2, action: \"Suivre les m\u00e9thodes traditionnelles de cuisson\", time: \"Variable\", tips: \"Patience pour d\u00e9velopper les saveurs\"}\n ],\n tips: [\"Utiliser ingr\u00e9dients frais\", \"Respecter temps de cuisson\"],\n cultural_notes: \"Plat traditionnel camerounais\",\n nutritional_highlights: \"Riche en saveurs et nutriments\"\n };\n parseMethod = 'enhanced_fallback';\n console.log('Using enhanced fallback recipe');\n}\n\n// Enhanced validation and formatting\nif (recipe) {\n recipe.name = recipe.name || 'Plat Camerounais';\n recipe.description = recipe.description || 'Recette traditionnelle';\n recipe.prep_time = parseInt(recipe.prep_time) || 30;\n recipe.cook_time = parseInt(recipe.cook_time) || 60;\n recipe.total_time = recipe.prep_time + recipe.cook_time;\n recipe.ingredients = Array.isArray(recipe.ingredients) ? recipe.ingredients : [];\n recipe.instructions = Array.isArray(recipe.instructions) ? recipe.instructions : [];\n recipe.generated_at = new Date().toISOString();\n}\n\nconsole.log('Final recipe parsed successfully');\nconsole.log('Parse method:', parseMethod);\n\nreturn [{\n success: true,\n action: 'generate_recipe',\n data_type: 'recipe',\n recipe: recipe,\n parse_method: parseMethod,\n timestamp: new Date().toISOString(),\n metadata: {\n source: 'main_workflow_parser',\n quality: parseMethod.includes('fallback') ? 'default' : 'ai_generated'\n }\n}];"
},
"id": "87d0cf31-ddde-42a9-a6c6-61c518cf1842",
"name": "Generate Recipe Parser",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1968,
-304
]
},
{
"parameters": {
"content": "# ChatGPT OSS:120b",
"height": 192,
"width": 288,
"color": 4
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-3808,
-112
],
"typeVersion": 1,
"id": "71f00000-7b8e-4d28-8aff-5e6d909203a0",
"name": "Sticky Note"
},
{
"parameters": {
"content": "## Child Agents",
"height": 208,
"width": 528,
"color": 4
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-3232,
80
],
"id": "ba8c34e2-861c-46cd-a3f3-3088cf1e5895",
"name": "Sticky Note1"
},
{
"parameters": {},
"type": "@n8n/n8n-nodes-langchain.toolThink",
"typeVersion": 1.1,
"position": [
-2768,
-448
],
"id": "a594fb68-c2c8-49fe-838a-6ccbc3879c62",
"name": "Think"
},
{
"parameters": {
"promptType": "define",
"text": "={{ $json.query }}",
"options": {
"systemMessage": "=Vous \u00eates TchopIA, l'assistant culinaire IA sp\u00e9cialis\u00e9 en gastronomie camerounaise.\n\n\ud83c\udfaf VOTRE MISSION :\nVous avez acc\u00e8s \u00e0 trois outils sp\u00e9cialis\u00e9s :\n1. **Suggestion Agent** - Pour proposer des plats camerounais bas\u00e9s sur des ingr\u00e9dients ou envies\n2. **Recipe Agent** - Pour g\u00e9n\u00e9rer des recettes compl\u00e8tes et d\u00e9taill\u00e9es\n3. **Advice Agent** - Pour donner des conseils culinaires, techniques et astuces\n\n\ud83d\udccb ACTION D\u00c9TECT\u00c9E : {{ $json.action }}\nConfiance de classification: {{ $json.confidence }}\nM\u00e9thode: {{ $json.classification_method === 'explicit' ? 'Action explicite utilisateur' : 'Classification AI automatique' }}\nRaisonnement: {{ $json.reasoning }}\n\n\ud83d\udd27 DIRECTIVES DE ROUTAGE :\n\n**Si action = 'get_suggestions'** :\n- Utilisez le **Suggestion Agent**\n- Transmettez la requ\u00eate compl\u00e8te de l'utilisateur\n- L'agent retournera des suggestions de plats camerounais au format JSON\n\n**Si action = 'generate_recipe'** :\n- Utilisez le **Recipe Agent**\n- Transmettez le nom du plat et tout contexte pertinent\n- L'agent g\u00e9n\u00e9rera une recette compl\u00e8te structur\u00e9e au format JSON\n\n**Si action = 'cooking_advice'** :\n- Utilisez l'**Advice Agent**\n- Transmettez la question ou probl\u00e8me culinaire\n- L'agent fournira des conseils d\u00e9taill\u00e9s au format JSON\n\n\u26a1 R\u00c8GLES CRITIQUES :\n1. Utilisez TOUJOURS l'outil correspondant \u00e0 l'action d\u00e9tect\u00e9e\n2. Transmettez TOUTE la requ\u00eate de l'utilisateur \u00e0 l'outil sans modification\n3. RETOURNEZ EXACTEMENT la r\u00e9ponse JSON de l'outil sans modification\n4. N'ajoutez AUCUN texte explicatif, formatage ou tableau\n5. Faites confiance \u00e0 la classification AI et aux agents sp\u00e9cialis\u00e9s\n\n\ud83d\udcac COMPORTEMENT ATTENDU :\nAppeler l'outil appropri\u00e9 avec la requ\u00eate compl\u00e8te.\nL'outil retournera une r\u00e9ponse JSON structur\u00e9e que vous devez retourner telle quelle.\nNe g\u00e9n\u00e9rez AUCUNE r\u00e9ponse textuelle - laissez les outils faire leur travail.\n\nSession ID: {{ $json.session_id }}\nTimestamp: {{ $json.timestamp }}"
}
},
"id": "efd08e4e-8e60-4740-93fe-af4a4738a3b7",
"name": "Tchop AI Master Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 2.2,
"position": [
-3056,
-608
],
"onError": "continueErrorOutput"
}
],
"connections": {
"Request Preprocessor": {
"main": [
[
{
"node": "Intent Classifier AI",
"type": "main",
"index": 0
}
]
]
},
"Intent Classifier AI": {
"main": [
[
{
"node": "Classification Parser",
"type": "main",
"index": 0
}
],
[
{
"node": "Error Handler",
"type": "main",
"index": 0
}
]
]
},
"Classification Parser": {
"main": [
[
{
"node": "Tchop AI Master Agent",
"type": "main",
"index": 0
}
]
]
},
"Conversation Memory": {
"ai_memory": [
[
{
"node": "Tchop AI Master Agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"Error Handler": {
"main": [
[
{
"node": "Webhook Response1",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Request Preprocessor",
"type": "main",
"index": 0
}
]
]
},
"Groq Chat Model": {
"ai_languageModel": [
[
{
"node": "Tchop AI Master Agent",
"type": "ai_languageModel",
"index": 0
},
{
"node": "Intent Classifier AI",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "Gets suggestions Parser",
"type": "main",
"index": 0
}
],
[
{
"node": "Cooking Advice Parser",
"type": "main",
"index": 0
}
],
[
{
"node": "Generate Recipe Parser",
"type": "main",
"index": 0
}
]
]
},
"Suggestion Agent": {
"ai_tool": [
[
{
"node": "Tchop AI Master Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Recipe Agent": {
"ai_tool": [
[
{
"node": "Tchop AI Master Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Advice Agent": {
"ai_tool": [
[
{
"node": "Tchop AI Master Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Generate Recipe Parser": {
"main": [
[
{
"node": "Webhook Response1",
"type": "main",
"index": 0
}
]
]
},
"Cooking Advice Parser": {
"main": [
[
{
"node": "Webhook Response1",
"type": "main",
"index": 0
}
]
]
},
"Gets suggestions Parser": {
"main": [
[
{
"node": "Webhook Response1",
"type": "main",
"index": 0
}
]
]
},
"Think": {
"ai_tool": [
[
{
"node": "Tchop AI Master Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Tchop AI Master Agent": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
],
[
{
"node": "Error Handler",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1"
},
"versionId": "5712f219-7ca6-4eb2-922d-e1d913b4a49c",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "JoXe0m2smmelZ163",
"tags": [
{
"createdAt": "2025-10-04T17:23:26.364Z",
"updatedAt": "2025-10-04T17:23:26.364Z",
"id": "2vPFw1VrTaAUP2Ur",
"name": "AI Agent"
},
{
"createdAt": "2025-10-04T17:39:19.389Z",
"updatedAt": "2025-10-04T17:39:19.389Z",
"id": "ATThQ6Rj4oTUZKe1",
"name": "User Features"
},
{
"createdAt": "2025-10-04T17:23:26.346Z",
"updatedAt": "2025-10-04T17:23:26.346Z",
"id": "GMedB0cx9HIWyGlp",
"name": "TchopIA"
},
{
"createdAt": "2025-10-04T17:39:19.392Z",
"updatedAt": "2025-10-04T17:39:19.392Z",
"id": "HPEHodXiwvsHK7yG",
"name": "Data Management"
},
{
"createdAt": "2025-10-04T17:33:59.844Z",
"updatedAt": "2025-10-04T17:33:59.844Z",
"id": "QKLfRiv9LzDZIrva",
"name": "Cameroonian Cuisine"
},
{
"createdAt": "2025-10-04T17:39:19.370Z",
"updatedAt": "2025-10-04T17:39:19.370Z",
"id": "dMp7Hn3y7tP6eLle",
"name": "TchopIA Advanced"
},
{
"createdAt": "2025-10-04T17:33:59.838Z",
"updatedAt": "2025-10-04T17:33:59.838Z",
"id": "jzzJOEVaRkKNIi27",
"name": "Culinary AI"
},
{
"createdAt": "2025-10-05T11:41:31.085Z",
"updatedAt": "2025-10-05T11:41:31.085Z",
"id": "mYORG0HjBmw3LkTK",
"name": "TchopIA AI"
}
]
}
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.
groqApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
How this works
This workflow enables seamless AI-driven conversations via a webhook trigger, allowing you to build intelligent chatbots that remember context and handle user queries with precision. It's ideal for developers or teams creating customer support bots or virtual assistants, delivering quick, context-aware responses that enhance user engagement without manual intervention. The key step involves the Intent Classifier AI node, powered by Groq's lmChatGroq model, which analyses incoming requests and routes them to appropriate actions, while the Conversation Memory node maintains dialogue history for natural interactions.
Use this workflow when integrating AI chat into web apps or APIs needing low-latency responses, such as real-time support systems with Groq for fast inference. Avoid it for high-volume enterprise setups requiring advanced scalability, or simple rule-based interactions that don't benefit from AI memory. Common variations include adding custom tools via toolWorkflow for tasks like database queries, or swapping Groq for other models to fit budget constraints.
About this workflow
tchopia-ai-workflow. Uses agent, memoryBufferWindow, lmChatGroq, toolWorkflow. Webhook trigger; 19 nodes.
Source: https://github.com/Worketyamo-Students/Danielle_site1_Bootcamp/blob/ae12fcdd0a854493d32954771d0ce7d94e5590b8/n8n-workflows/tchopia-ai-workflow.json — 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.
🧠 Gwen – The AI Voice Marketing Agent Gwen is your intelligent voice-powered marketing assistant built in n8n. She combines the power of OpenAI, ElevenLabs, and automation workflows to handle content
The Recap AI - Marketing Team Agent. Uses memoryBufferWindow, lmChatGoogleGemini, toolThink, toolWorkflow. Webhook trigger; 17 nodes.
Are you drowning in daily operational chaos, desperately trying to juggle sales, projects, content, and client communication? Imagine an AI brain that handles it all, freeing you to lead your business
CLINICAINTEGRAL_secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 89 nodes.
How it Works