Difyでは成功しているのに食品が『未登録』と表示された原因と解決方法
Reppalの栄養計算機能で、APIは食品を正常に取得しているのにDifyでは『未登録』と表示された問題について、原因の切り分け、JSONの項目名をそろえる修正、再発防止策をまとめます。
食事・体重記録アプリ「Reppal」では、入力された食品をデータベースから検索し、量に応じたカロリーや栄養素を計算する機能を開発しています。
開発中、食品検索APIでは卵を正常に取得できているにもかかわらず、Difyが生成する回答では「卵は未登録です」と表示される問題が発生しました。
原因はデータベースでも食品名でもなく、APIが返すJSONとDify側が参照する項目名の不一致でした。
この記事では、問題が発生したときの状況、原因を特定するまでに確認したこと、実施した修正、同じ問題を防ぐための方法をまとめます。
Reppalの栄養計算の構成
Reppalでは、自然な文章から食品名、量、単位を取り出し、Supabaseに登録した食品データを使って栄養価を計算します。
当時の大まかな処理の流れは次のとおりでした。
利用者の入力
↓
Difyで食品名・量・単位を抽出
↓
Supabase Edge Functionへ送信
↓
foodsテーブルから食品を検索
↓
量に応じた栄養価を計算
↓
Difyで回答文を作成
たとえば「卵を1個食べた」と入力した場合、Difyで次のようなデータへ変換します。
{
"name": "卵",
"amount": 1,
"unit": "個"
}
Edge Functionでは「卵」と「個」に対応するデータを検索し、1個分の重量へ換算してカロリーを計算します。
発生した問題
卵の栄養計算を試したところ、APIのレスポンスには正常な結果が含まれていました。
実際のレスポンスを簡略化すると、次のような内容です。
{
"name": "卵",
"found": true,
"calculated_kcal": 80
}
found は true で、計算後のカロリーも calculated_kcal に入っています。
この結果だけを見ると、食品検索と栄養計算は成功しています。しかし、Difyの最終回答では次のような内容が表示されました。
卵は食品データベースに登録されていません。
APIでは成功しているのに、画面上では失敗として扱われる状態です。
最初に疑ったこと
食品が未登録と表示されたため、最初は次のような可能性を考えました。
foodsテーブルに卵が登録されていない- 「卵」と「たまご」の表記が一致していない
- Difyが食品名を正しく抽出できていない
- 単位「個」に対応する重量が登録されていない
- Edge Functionの検索条件に誤りがある
そこで、処理を一つずつ分けて確認しました。
原因を切り分ける
複数のサービスをつないだ処理では、画面に表示された結果だけを見ても、どの段階で問題が起きたのか分かりません。
今回は次の順番で確認しました。
1. Difyが作った入力を確認する
DifyからEdge Functionへ渡している食品名、量、単位を確認します。
{
"foods": [
{
"name": "卵",
"amount": 1,
"unit": "個"
}
]
}
この段階では、卵が正しく抽出されていました。
2. Edge Functionのレスポンスを確認する
次に、Edge Functionが返したJSONを確認しました。
{
"results": [
{
"name": "卵",
"found": true,
"calculated_kcal": 80
}
]
}
食品検索にも成功しており、カロリーも計算されています。
ここまで確認したことで、問題はSupabaseの検索処理ではなく、APIのレスポンスを受け取ったあとの処理にあると判断できました。
3. Difyの後続ノードを確認する
Difyでは、HTTPリクエストの結果を後続のコードノードで整形していました。
ところが、コードノードは次のような項目を参照していました。
kcal = food.get("kcal")
一方、APIが返していた項目名は次のとおりです。
calculated_kcal
Dify側が探している kcal はレスポンスに存在しないため、値は None になります。その結果、後続処理が「栄養価を取得できなかった食品」と判断していました。
原因はJSONの項目名の不一致
今回の原因を表にすると、次のようになります。
| 内容 | APIが返す項目 | Difyが参照する項目 |
|---|---|---|
| 検索結果 | found | found |
| カロリー | calculated_kcal | kcal |
found は一致していましたが、カロリーの項目名が一致していませんでした。
API、Dify、アプリをそれぞれ個別に見ると正常に見えても、サービス間で受け渡すデータの形式がずれていると、全体としては正しく動作しません。
解決方法
解決方法は、APIが返す項目名とDify側が参照する項目名を統一することです。
今回はDify側でAPIのレスポンスを整形し、後続処理が利用する形式へ変換しました。
修正前
修正前は kcal を直接参照していました。
def main(api_response: dict) -> dict:
foods = []
for food in api_response.get("results", []):
foods.append({
"name": food.get("name"),
"found": food.get("found", False),
"kcal": food.get("kcal"),
})
return {"foods": foods}
APIのレスポンスに kcal が存在しないため、カロリーを取得できません。
修正後
APIが返す calculated_kcal を参照するよう修正します。
def main(api_response: dict) -> dict:
foods = []
for food in api_response.get("results", []):
foods.append({
"name": food.get("name"),
"found": food.get("found", False),
"kcal": food.get("calculated_kcal"),
})
return {"foods": foods}
これにより、後続ノードでは統一された kcal という名前で値を利用できます。
{
"foods": [
{
"name": "卵",
"found": true,
"kcal": 80
}
]
}
修正後は、卵が未登録と判定されず、計算結果を回答へ含められるようになりました。
API側を修正する方法もある
Dify側ではなく、Edge Functionのレスポンスを変更する方法もあります。
たとえば、API側で最初から kcal として返します。
return {
name: food.name,
found: true,
kcal: calculatedKcal,
};
どちらを修正しても動作しますが、大切なのは、どの層でどの形式を使うかを決めることです。
Reppalでは、食品ごとの基準値と、入力された量から計算した値を区別したかったため、API内部では calculated_kcal のような名前を使い、Difyへ渡す段階で表示用の形式に変換する構成としました。
一時的に両方の項目へ対応する方法
APIとDifyを同時に更新できない場合は、移行期間だけ両方の項目を受け付ける方法もあります。
kcal = food.get("calculated_kcal")
if kcal is None:
kcal = food.get("kcal")
短く書く場合でも、0 を正しい値として扱う可能性があるため、単純な or より None を明示的に確認する方が安全です。
def get_kcal(food: dict):
calculated_kcal = food.get("calculated_kcal")
if calculated_kcal is not None:
return calculated_kcal
return food.get("kcal")
ただし、複数の形式をいつまでも残すと処理が複雑になります。移行が完了したら、正式な形式へ統一した方が管理しやすくなります。
found だけで判定する
今回の処理では、食品が登録されているかどうかと、カロリーの値を取得できたかどうかが混ざっていました。
食品の登録状態を判定する場合は、基本的に found を確認します。
if not food.get("found", False):
# 未登録食品として扱う
pass
次のようにカロリーの有無だけで登録状態を判定すると、項目名の変更や値の欠落によって、登録済みの食品まで未登録と判断する可能性があります。
# 登録状態の判定としては不安定
if food.get("kcal") is None:
# 未登録として扱う
pass
「食品が見つかったか」と「栄養価を計算できたか」は、別々に扱う方が原因を特定しやすくなります。
{
"name": "卵",
"found": true,
"calculation_success": true,
"calculated_kcal": 80,
"error": null
}
たとえば食品は見つかったものの、単位情報が足りず計算できなかった場合は、次のように返せます。
{
"name": "卵",
"found": true,
"calculation_success": false,
"calculated_kcal": null,
"error": "単位『個』の換算情報がありません"
}
この形式であれば、未登録なのか、単位変換に失敗したのかを区別できます。
修正後に確認したこと
修正後は、正常な食品だけでなく、意図的に失敗する入力も確認しました。
- 登録済みの食品を正しく表示できるか
calculated_kcalがDifyへ渡っているか- 未登録食品は未登録として表示されるか
- 食品は存在するが単位が不明な場合を区別できるか
- 複数の食品を入力しても結果がずれないか
- 0や小数の値を欠損値として扱っていないか
正常系だけでなく、未登録、単位不足、値の欠損なども試すことで、修正による別の問題がないことを確認できます。
再発を防ぐために行ったこと
今回のような問題を防ぐため、次の点を意識するようにしました。
APIのレスポンス例を残す
Edge Functionが返すJSONの例を残し、Difyの各ノードがどの項目を参照するか確認できるようにします。
{
"results": [
{
"name": "白米",
"found": true,
"calculated_kcal": 234
}
]
}
ノードごとの入出力を確認する
Difyのワークフロー全体だけではなく、次の各段階を個別に確認します。
- 開始ノードへ渡された値
- 食品抽出ノードの出力
- HTTPリクエストの本文
- Edge Functionのレスポンス
- コードノードで整形した結果
- 最終回答へ渡した変数
データがどこまで正常だったかを確認すると、問題の範囲を絞りやすくなります。
エラーと未登録を分ける
食品が見つからなかった場合と、処理中にエラーが発生した場合は別の状態です。
{
"found": false,
"error": null
}
{
"found": false,
"error": "食品検索APIへの接続に失敗しました"
}
両方を「未登録」と表示すると、本当の原因が利用者にも開発者にも分かりにくくなります。
型と項目名を一か所で変換する
各ノードで個別に項目名を変えると、どの名前が正式なのか分からなくなります。
そのため、APIレスポンスを受け取った直後のコードノードで表示用の形式へ変換し、それ以降は同じ形式を使う構成にしました。
今回学んだこと
画面に「食品未登録」と表示されても、原因が食品データベースにあるとは限りません。
今回の問題では、次の処理はすべて成功していました。
- Difyによる食品名の抽出
- Supabaseでの食品検索
- 単位を使った数量の換算
- カロリー計算
- Edge Functionからのレスポンス返却
失敗していたのは、返された結果をDifyの後続ノードで読み取る部分だけでした。
複数のサービスを連携するときは、「成功したか、失敗したか」だけを見るのではなく、各段階で実際にどのJSONが渡されているかを確認することが重要です。
また、項目名が一文字違うだけでも処理はつながりません。APIの入出力も一つの仕様として管理し、変更した場合は利用しているすべての処理を確認する必要があります。
まとめ
Reppalで食品が「未登録」と表示された原因は、APIが返す calculated_kcal と、Dify側が参照する kcal が一致していなかったことでした。
解決のために行ったことは次のとおりです。
- DifyからAPIへ渡した値を確認する
- Edge Functionのレスポンスを確認する
- Difyの後続ノードが参照する項目名を確認する
calculated_kcalを表示用のkcalへ変換するfoundと計算結果を分けて判定する- 正常系と異常系の両方を再テストする
最初は食品データや検索処理の問題だと考えていましたが、実際にはサービス間のJSON形式が原因でした。
今回の経験をもとに、ReppalではAPIのレスポンス形式を明確にし、各処理の入出力を個別に確認しながら開発を進めています。