Inferir ingredientes y estimar calorías a partir de imágenes de alimentos
Este es unautomatización que contiene 16 nodos.Utiliza principalmente nodos como Code, Gmail, Agent, TelegramTrigger, LmChatOpenRouter. Estimación de calorías mediante análisis de imágenes de comida con IA visual y un bot de Telegram
- •Cuenta de Google y credenciales de API de Gmail
- •Bot Token de Telegram
Nodos utilizados (16)
Categoría
{
"id": "aZU2RbdXlp3eXCZh",
"meta": {
"instanceId": "15d6057a37b8367f33882dd60593ee5f6cc0c59310ff1dc66b626d726083b48d",
"templateCredsSetupCompleted": true
},
"name": "Infer Ingredients from a Food Image and Estimate Calories",
"tags": [],
"nodes": [
{
"id": "agent1",
"name": "Agente de IA - Análisis de ingredientes",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
752,
400
],
"parameters": {
"text": "=あなたは料理と栄養の専門家です。提供された料理の画像を分析して、以下の情報を日本語で提供してください:\n\n1. 料理名\n2. 推定される主な食材とその分量(グラム)\n3. 各食材のカロリー\n4. 合計推定カロリー\n5. 栄養バランスの簡単な評価\n\n画像URL: {{ $json.message.photo[0].file_id }}",
"options": {
"systemMessage": "以下の指示に従って、ユーザーが入力した料理の情報を分析し、JSON形式で出力してください。\n\n1. **役割:** 料理レシピアナライザー\n2. **入力:** ユーザーからの料理名や食材に関するテキスト\n3. **処理:**\n * 料理名を特定する。\n * 食材リストを抽出し、各食材の「名前(name)」「分量(amount)」「カロリー(calories)」を特定する。分量とカロリーは一般的な値を推定してもよい。\n * 全食材のカロリーを合計し、「合計カロリー(totalCalories)」を計算する。\n * 計算結果に基づき、「栄養バランスの評価(nutritionEvaluation)」を作成する。\n4. **出力:** 以下のJSONスキーマに厳密に従ったJSONオブジェクトのみを出力する。\n * `dishName`: string\n * `ingredients`: array of objects\n * `name`: string\n * `amount`: number (単位: g)\n * `calories`: number (単位: kcal)\n * `totalCalories`: number (単位: kcal)\n * `nutritionEvaluation`: string\n5. **注意:**\n * 出力にJSON以外のテキスト(挨拶、説明など)を含めないでください。\n * JSONのキー名は指定通りにしてください。"
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2
},
{
"id": "parser1",
"name": "Analizador de salida estructurada",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
912,
608
],
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"dishName\": {\n \"type\": \"string\",\n \"description\": \"料理名\"\n },\n \"ingredients\": {\n \"type\": \"array\",\n \"description\": \"食材リスト\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"description\": \"食材名\"\n },\n \"amount\": {\n \"type\": \"number\",\n \"description\": \"分量(グラム)\"\n },\n \"calories\": {\n \"type\": \"number\",\n \"description\": \"カロリー(kcal)\"\n }\n }\n }\n },\n \"totalCalories\": {\n \"type\": \"number\",\n \"description\": \"合計カロリー(kcal)\"\n },\n \"nutritionEvaluation\": {\n \"type\": \"string\",\n \"description\": \"栄養バランスの評価\"\n }\n },\n \"required\": [\"dishName\", \"ingredients\", \"totalCalories\", \"nutritionEvaluation\"]\n}"
},
"typeVersion": 1.2
},
{
"id": "52bb2509-5870-4f4c-96ea-c607548b15b1",
"name": "Telegram Trigger",
"type": "n8n-nodes-base.telegramTrigger",
"position": [
480,
400
],
"webhookId": "f6a51604-9b18-4756-a50e-1e5eb1f8da6f",
"parameters": {
"updates": [
"message"
],
"additionalFields": {
"download": true
}
},
"credentials": {
"telegramApi": {
"id": "Ikb8BOl71y5C8wqu",
"name": "Telegram account"
}
},
"typeVersion": 1.2
},
{
"id": "a8ee11fd-47bc-4524-89a5-a19380041613",
"name": "Modelo de chat OpenRouter",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
608,
608
],
"parameters": {
"model": "openai/gpt-5-mini",
"options": {
"temperature": 0.3
}
},
"credentials": {
"openRouterApi": {
"id": "fMR5QJezr3tD108w",
"name": "簡易デモ"
}
},
"typeVersion": 1
},
{
"id": "7397c3e2-c057-4f6b-9afa-010b0a610789",
"name": "Format for Gmail",
"type": "n8n-nodes-base.code",
"position": [
1152,
400
],
"parameters": {
"jsCode": "// AI Agentから出力されたJSON形式の料理分析結果を取得します。\nconst analysisResult = $input.first().json.output;\n\n// 結果が存在しない、または期待した形式でない場合はエラーを防ぐための処理\nif (!analysisResult || !analysisResult.dishName) {\n return [{\n json: {\n subject: \"料理分析エラー\",\n htmlBody: \"<p>AIによる分析結果を取得できませんでした。</p>\"\n }\n }];\n}\n\n// 1. メールの件名を作成\nconst subject = `【料理分析レポート】 ${analysisResult.dishName}`;\n\n// 2. HTMLメールの本文を生成\nlet htmlBody = `\n<!DOCTYPE html>\n<html lang=\"ja\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <style>\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; line-height: 1.6; color: #333; background-color: #f9f9f9; margin: 0; padding: 0; }\n .container { max-width: 600px; margin: 20px auto; padding: 25px; background-color: #ffffff; border: 1px solid #e0e0e0; border-radius: 12px; box-shadow: 0 4px 8px rgba(0,0,0,0.05); }\n h1 { font-size: 26px; color: #2c3e50; border-bottom: 3px solid #3498db; padding-bottom: 10px; margin-top: 0; }\n h2 { font-size: 20px; color: #e74c3c; margin-bottom: 15px; }\n h3 { font-size: 18px; color: #34495e; margin-top: 30px; border-bottom: 1px solid #eeeeee; padding-bottom: 8px;}\n p { margin-top: 0; color: #555; }\n table { width: 100%; border-collapse: collapse; margin-top: 15px; }\n th, td { text-align: left; padding: 12px; border-bottom: 1px solid #dddddd; }\n th { background-color: #f2f2f2; color: #333; }\n tr:last-child td { border-bottom: none; }\n .total-calories { text-align: center; background-color: #fff3cd; padding: 15px; border-radius: 8px; margin-top: 20px; }\n .evaluation { background-color: #eafaf1; padding: 15px; border-left: 5px solid #2ecc71; border-radius: 5px; margin-top: 20px;}\n .footer { text-align: center; margin-top: 25px; font-size: 12px; color: #999; }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <h1>${analysisResult.dishName}</h1>\n \n <div class=\"total-calories\">\n <h2>合計推定カロリー: ${analysisResult.totalCalories} kcal</h2>\n </div>\n\n <h3>詳細な食材リスト</h3>\n <table>\n <thead>\n <tr>\n <th>食材名</th>\n <th>分量 (g)</th>\n <th>カロリー (kcal)</th>\n </tr>\n </thead>\n <tbody>\n`;\n\n// 各食材の情報をテーブルの行として追加\nif (analysisResult.ingredients && analysisResult.ingredients.length > 0) {\n analysisResult.ingredients.forEach(ingredient => {\n htmlBody += `\n <tr>\n <td>${ingredient.name}</td>\n <td>${ingredient.amount}</td>\n <td>${ingredient.calories}</td>\n </tr>\n `;\n });\n}\n\nhtmlBody += `\n </tbody>\n </table>\n\n <h3>栄養バランスの評価</h3>\n <div class=\"evaluation\">\n <p>${analysisResult.nutritionEvaluation}</p>\n </div>\n\n <div class=\"footer\">\n <p>このレポートはAIによって自動生成されました。</p>\n </div>\n </div>\n</body>\n</html>\n`;\n\n// 3. 後続のGmailノードで使えるようにデータを返す\nreturn [{\n json: {\n subject: subject,\n htmlBody: htmlBody\n }\n}];"
},
"typeVersion": 2
},
{
"id": "ae0d956b-599d-4bba-b14b-67245e3287fb",
"name": "Enviar un mensaje1",
"type": "n8n-nodes-base.gmail",
"position": [
1408,
400
],
"webhookId": "b0128308-fba0-4291-996d-e38ac9edaed4",
"parameters": {
"sendTo": "t.minamig20@gmail.com",
"message": "={{ $json.htmlBody }}",
"options": {
"appendAttribution": false
},
"subject": "={{ $json.subject }}"
},
"credentials": {
"gmailOAuth2": {
"id": "qEtK64Ero6pOyPe0",
"name": "Gmail account 16"
}
},
"typeVersion": 2.1
},
{
"id": "9be8d7fb-d689-41bb-b638-755bddd7a618",
"name": "Nota adhesiva 1: Descripción general",
"type": "n8n-nodes-base.stickyNote",
"position": [
-16,
80
],
"parameters": {
"color": 5,
"width": 520,
"height": 260,
"content": "## 🗒️ Sticky 1: Overview\n**What this template does**\n- Accepts a **food image** and runs **AI-based ingredient inference** → **calorie estimation** → **concise summary**\n- Input can be an **image URL** or **Base64 image** (via Webhook or a messaging app)\n\n**Why it’s useful**\n- Quickly generates a calorie rough estimate and a short nutrition comment for logging, sharing, or feedback loops\n\n**No hardcoded secrets**\n- All API keys must be set via **Credentials/Environment Variables** (never hardcode keys in nodes)"
},
"typeVersion": 1
},
{
"id": "0bd3d0eb-a030-4d79-a390-17e41d705f7e",
"name": "Nota adhesiva 2: Prerrequisitos",
"type": "n8n-nodes-base.stickyNote",
"position": [
608,
-16
],
"parameters": {
"color": 4,
"width": 520,
"height": 280,
"content": "## 🗒️ Sticky 2: Prerequisites & Credentials\n**Connections**\n- LLM (OpenAI or compatible) with **vision** capability\n- One input channel (Webhook **or** Telegram/LINE, etc.)\n\n**Environment Variables (examples)**\n- `LLM_MODEL` (e.g., `gpt-4o` or any vision-capable model)\n- `LLM_TEMPERATURE` (e.g., `0.3`)\n- `WEBHOOK_SECRET` (optional, if you validate requests)\n\n**Scopes / Permissions**\n- If image URLs are external, ensure they are publicly accessible or use signed/temporary URLs"
},
"typeVersion": 1
},
{
"id": "ff6d1495-3223-4305-a354-57a22adf71f3",
"name": "Nota adhesiva 3: Formato de entrada",
"type": "n8n-nodes-base.stickyNote",
"position": [
-144,
560
],
"parameters": {
"color": 2,
"width": 520,
"height": 300,
"content": "## 🗒️ Sticky 3: Input Format (Sample)\n**Expected JSON**\n```json\n{\n \"imageUrl\": \"https://example.com/dish.jpg\"\n // or\n // \"imageBase64\": \"data:image/jpeg;base64,....\"\n}\n```\n**Notes**\n- Template assumes **one image** per request (extend with arrays for multiple images)\n- If you receive platform-specific payloads (e.g., Telegram `file_id`, LINE message objects), **normalize** them first to `imageUrl` or `imageBase64` before calling the LLM"
},
"typeVersion": 1
},
{
"id": "1bd2ee7d-a5f9-42f4-9974-4698fb05b553",
"name": "Nota adhesiva 4: Modelo y prompt",
"type": "n8n-nodes-base.stickyNote",
"position": [
528,
864
],
"parameters": {
"color": 3,
"width": 520,
"height": 260,
"content": "## 🗒️ Sticky 4: Model & Prompt Policy\n**Model**\n- Use a **vision-capable** LLM (e.g., `gpt-4o`)\n- Keep `temperature` low (0.2–0.3) for stable outputs\n\n**Prompt Rules**\n- The model must return **strict JSON only**, with the fields below (no extra text)\n- Keep names concise (e.g., \"grilled chicken\", \"white rice\", \"mixed salad\")\n- Provide amounts in grams when possible (estimates are fine)"
},
"typeVersion": 1
},
{
"id": "84f0f90c-e819-4022-a76e-b63353ca9968",
"name": "Nota adhesiva 5: Esquema de salida",
"type": "n8n-nodes-base.stickyNote",
"position": [
1184,
-16
],
"parameters": {
"width": 520,
"height": 280,
"content": "## 🗒️ Sticky 5: Output Schema (Fixed)\n**JSON structure**\n```json\n{\n \"dishName\": \"string\",\n \"ingredients\": [\n { \"name\": \"string\", \"amount\": 0, \"calories\": 0 }\n ],\n \"totalCalories\": 0,\n \"nutritionEvaluation\": \"string\"\n}\n```\n**Validation**\n- If fields are empty/missing, route to an **error branch** and return a helpful message for the user"
},
"typeVersion": 1
},
{
"id": "fe6e558b-4f6a-4c0a-9b06-e3939b07be3f",
"name": "Nota adhesiva 6: Pasos de prueba",
"type": "n8n-nodes-base.stickyNote",
"position": [
1200,
816
],
"parameters": {
"color": 5,
"width": 520,
"height": 220,
"content": "## 🗒️ Sticky 6: Test Steps (Under 1 Minute)\n1) Turn on the **Webhook** node and copy the **Test URL**\n2) Send a sample payload with a valid `imageUrl`\n3) Confirm the run returns the **strict JSON** structure\n4) If using Telegram/Slack replies, authenticate and send a test message to verify delivery"
},
"typeVersion": 1
},
{
"id": "24bbd216-9c8b-4a4f-b446-dd82585117fd",
"name": "Nota adhesiva 7: Errores y límites",
"type": "n8n-nodes-base.stickyNote",
"position": [
1696,
0
],
"parameters": {
"color": 4,
"width": 520,
"height": 260,
"content": "## 🗒️ Sticky 7: Error Handling & Limits\n**Retries**\n- For LLM calls, implement **exponential backoff** on 429/5xx\n- If the image fetch fails, **retry**, then go to **error branch**\n\n**Limits**\n- Watch for model **context/vision limits**; downscale/thumbnail images if needed\n- When rate-limited, use **Delay** + **Retry** to recover gracefully"
},
"typeVersion": 1
},
{
"id": "55619913-3b70-43d8-ab38-f5b8eb4a6375",
"name": "Nota adhesiva 8: Seguridad",
"type": "n8n-nodes-base.stickyNote",
"position": [
1776,
784
],
"parameters": {
"color": 3,
"width": 520,
"height": 220,
"content": "## 🗒️ Sticky 8: Security & PII\n- Store API keys/tokens in **Credentials/Env Vars** (never in node fields)\n- If image URLs are private, use **signed/temporary URLs**\n- **Do not log** personal data; mask/remove PII from execution logs\n- Use **HTTPS only** for all external requests"
},
"typeVersion": 1
},
{
"id": "95833663-f776-40c5-9225-30f07e580acb",
"name": "Nota adhesiva 9: Entrega",
"type": "n8n-nodes-base.stickyNote",
"position": [
2272,
0
],
"parameters": {
"color": 2,
"width": 520,
"height": 220,
"content": "## 🗒️ Sticky 9: Delivery Options (No Gmail)\n- Reply via **Telegram/Slack** or return via **HTTP Response**\n- Optionally log results to **Google Sheets** or **Notion** for history/analytics\n- If email is required, use **SMTP** or a non-Gmail provider (per your stack)"
},
"typeVersion": 1
},
{
"id": "d1f31505-89ed-4483-bb52-d1f98c02db36",
"name": "Nota adhesiva 10: Extensibilidad",
"type": "n8n-nodes-base.stickyNote",
"position": [
1840,
400
],
"parameters": {
"width": 520,
"height": 240,
"content": "## 🗒️ Sticky 10: Extensibility (Review Bonus)\n- **Multilingual output** (ja/en) switch\n- Normalize serving units (piece/slice) → **gram conversion**\n- Categorize components (main dish / side / sauce)\n- Add **evidence-based** nutrition comments (e.g., high fat/high carb) with short rationale"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"timezone": "Asia/Tokyo",
"errorWorkflow": "",
"executionOrder": "v1",
"saveManualExecutions": true,
"saveExecutionProgress": true,
"saveDataErrorExecution": "all",
"saveDataSuccessExecution": "all"
},
"versionId": "8766f935-4d36-4d19-9f54-9179e0a59262",
"connections": {
"7397c3e2-c057-4f6b-9afa-010b0a610789": {
"main": [
[
{
"node": "ae0d956b-599d-4bba-b14b-67245e3287fb",
"type": "main",
"index": 0
}
]
]
},
"52bb2509-5870-4f4c-96ea-c607548b15b1": {
"main": [
[
{
"node": "agent1",
"type": "main",
"index": 0
}
]
]
},
"a8ee11fd-47bc-4524-89a5-a19380041613": {
"ai_languageModel": [
[
{
"node": "agent1",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"agent1": {
"main": [
[
{
"node": "7397c3e2-c057-4f6b-9afa-010b0a610789",
"type": "main",
"index": 0
}
]
]
},
"parser1": {
"ai_outputParser": [
[
{
"node": "agent1",
"type": "ai_outputParser",
"index": 0
}
]
]
}
}
}¿Cómo usar este flujo de trabajo?
Copie el código de configuración JSON de arriba, cree un nuevo flujo de trabajo en su instancia de n8n y seleccione "Importar desde JSON", pegue la configuración y luego modifique la configuración de credenciales según sea necesario.
¿En qué escenarios es adecuado este flujo de trabajo?
Avanzado
¿Es de pago?
Este flujo de trabajo es completamente gratuito, puede importarlo y usarlo directamente. Sin embargo, tenga en cuenta que los servicios de terceros utilizados en el flujo de trabajo (como la API de OpenAI) pueden requerir un pago por su cuenta.
Flujos de trabajo relacionados recomendados
Toshiya Minami
@minamiCompartir este flujo de trabajo