8
n8n 한국어amn8n.com

음식 이미지에서 재료를 추론하고 칼로리를 추산

고급

이것은자동화 워크플로우로, 16개의 노드를 포함합니다.주로 Code, Gmail, Agent, TelegramTrigger, LmChatOpenRouter 등의 노드를 사용하며. 시각 AI와 Telegram을 사용하여 음식 이미지 분석을 통해 칼로리 추정

사전 요구사항
  • Google 계정 및 Gmail API 인증 정보
  • Telegram Bot Token

카테고리

-
워크플로우 미리보기
노드 연결 관계를 시각적으로 표시하며, 확대/축소 및 이동을 지원합니다
워크플로우 내보내기
다음 JSON 구성을 복사하여 n8n에 가져오면 이 워크플로우를 사용할 수 있습니다
{
  "id": "aZU2RbdXlp3eXCZh",
  "meta": {
    "instanceId": "15d6057a37b8367f33882dd60593ee5f6cc0c59310ff1dc66b626d726083b48d",
    "templateCredsSetupCompleted": true
  },
  "name": "Infer Ingredients from a Food Image and Estimate Calories",
  "tags": [],
  "nodes": [
    {
      "id": "agent1",
      "name": "AI Agent - 재료 분석",
      "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": "Structured Output Parser",
      "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": "OpenRouter Chat Model",
      "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": "메시지 보내기1",
      "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": "Sticky 1: 개요",
      "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": "Sticky 2: 필수 조건",
      "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": "Sticky 3: 입력 형식",
      "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": "Sticky 4: 모델 및 프롬프트",
      "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": "Sticky 5: 출력 스키마",
      "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": "Sticky 6: 테스트 단계",
      "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": "Sticky 7: 오류 및 제한사항",
      "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": "Sticky 8: 보안",
      "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": "Sticky 9: 전달",
      "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": "Sticky 10: 확장성",
      "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
          }
        ]
      ]
    }
  }
}
자주 묻는 질문

이 워크플로우를 어떻게 사용하나요?

위의 JSON 구성 코드를 복사하여 n8n 인스턴스에서 새 워크플로우를 생성하고 "JSON에서 가져오기"를 선택한 후, 구성을 붙여넣고 필요에 따라 인증 설정을 수정하세요.

이 워크플로우는 어떤 시나리오에 적합한가요?

고급

유료인가요?

이 워크플로우는 완전히 무료이며 직접 가져와 사용할 수 있습니다. 다만, 워크플로우에서 사용하는 타사 서비스(예: OpenAI API)는 사용자 직접 비용을 지불해야 할 수 있습니다.

워크플로우 정보
난이도
고급
노드 수16
카테고리-
노드 유형7
난이도 설명

고급 사용자를 위한 16+개 노드의 복잡한 워크플로우

외부 링크
n8n.io에서 보기

이 워크플로우 공유

카테고리

카테고리: 34