食品画像から食材を推定しカロリーを計算
上級
これは自動化ワークフローで、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": "Send a message1",
"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など)は別途料金が発生する場合があります。
関連ワークフロー
顧客調査サマリージェネレーター
客観調査のフィードバックをAI、Google Sheets、Slackで分析する
Code
Slack
Google Sheets
+
Code
Slack
Google Sheets
16 ノードToshiya Minami
AIマーケティングエージェント:Reddit + OpenRouter + Gmail によるリードジェネレーション
AIマーケティングエージェント:Reddit、OpenRouter、Gmailを活用したリードジェネレーション
If
Set
Code
+
If
Set
Code
23 ノードAri Nakos
営業
AI研究エージェント:Mistral最適OCRによるPDF自動分析
AI研究エージェント:Mistral最適OCRによるPDF自動分析
Set
Code
Gmail
+
Set
Code
Gmail
30 ノードDerek Cheung
人工知能
Danelfin、TwelveData、Alpha Vantage を基にした AI 株式分析
Danelfin、TwelveData、Alpha Vantage を統合した AI 株式分析システム
Set
Code
Gmail
+
Set
Code
Gmail
74 ノードPaul
仮想通貨取引
X(Twitter)コンテンツエンジン自動化
AIベースのX(Twitter)コンテンツ生成とスケジューリング(LangChain、Blotato)
If
Code
Gmail
+
If
Code
Gmail
24 ノードYUSUKE YAMAMOTO
AI、Apify、Telegram統合による自動化ツールでの評価
Telegram、Apify、AI、Googleスプレッドシートを使ってWebサイトツール分析を自動化
If
Set
Code
+
If
Set
Code
20 ノードMirza Ajmal
AI要約