Open Food Facts Omi 集成:为 Omi 聊天添加免登录的食品信息查询工具
2026/9/17 6:06:01 网站建设 项目流程

Open Food Facts Omi 集成:为 Omi 聊天添加免登录的食品信息查询工具

【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend

导读

本文以仓库中plugins/omi-openfoodfacts-app/目录下的 README.md 为核心,系统讲解该独立部署的 Omi 插件服务如何基于 Open Food Facts 公开只读 API,为 Omi 聊天提供按名称搜索食品、按条形码查询、多商品对比与过敏原核查四大能力。读完本文,你将掌握该插件的工具清单与 Omi 应用配置方式、四个聊天工具的请求/响应契约、全部环境变量语义、本地开发与 Railway 部署流程,并能结合 main.py 源码理解其数据字段选取、营养数据口径、搜索端点选择与过敏原匹配的底层实现细节。

一、插件定位:Omi 生态中的轻量无账号查询服务

在 Omi 插件体系中,omi-*-app/是一批相互独立部署的 FastAPI 服务,每个目录都自带main.py、依赖文件与部署描述(Dockerfile / Procfile / railway.toml),互不依赖、独立上线(见 plugins/README.md)。omi-openfoodfacts-app正是其中之一,它的独特之处在于:

  • 零账号接入:完全基于 Open Food Facts 的只读 API,不要求用户连接任何账号,不需要 OAuth,也不需要 API Key;
  • 对话内直用:通过 Omi 的 Chat Tools 机制,用户在聊天中直接发问即可触发查询;
  • 食品领域专精:聚焦包装食品的配料、营养、评分与过敏原信息,返回结构化的产品摘要。

与需要安装 omi-plugin-sdk 的 webhook 型插件不同,本插件是“独立部署的 chat tools 服务”,通过暴露.well-known/omi-tools.json清单向 Omi 声明能力,属于插件体系中“独立部署服务”这一类。

二、功能总览:五类开箱即用的食品查询能力

依据 README,该插件提供以下能力,全部由只读 Open Food Facts API 支撑:

  1. 按名称搜索包装食品:例如搜索“燕麦奶”;
  2. 按条形码查询单个产品:读取包装上的 EAN 码即可命中数据库记录;
  3. 最多五个条形码的产品对比:横向比较营养、评分与标签;
  4. 过敏原核查:将某条形码(或搜索结果第一条)中的过敏原、致敏痕迹与配料文本,与用户想要避开的食物词表进行匹配;
  5. 返回结构化营养与评级信息:每 100g 营养数据、Nutri-Score、NOVA 加工分级、Eco-Score、标签、类别、配料文本以及产品正面小图(若存在)。

全部调用均为只读请求,无需 OAuth 或 API Key——这是该项目最重要的集成前提,意味着部署成本极低,用户可以即刻在对话中体验。

三、Omi 应用配置:三行填表即可接入

在 Omi 平台创建应用时,按 README 给出的字段填写:

字段取值
Chat Tools Manifest URLhttps://YOUR-APP.up.railway.app/.well-known/omi-tools.json
Setup URL留空
Setup Completed URL留空

其中的YOUR-APP需替换为你实际部署的 Railway 服务域名。由于本插件无需账号绑定,因此两个 Setup 相关 URL 都保持空白,Omi 在识别到工具清单后即可直接调用。

/ .well-known/omi-tools.json这个路径由 main.py 中的get_omi_tools_manifest()直接返回(同时提供了/manifest.json别名端点),清单内每个工具都声明了名称、描述、HTTP 端点、方法、参数 schema、auth_required: Falsestatus_message,这正是 Omi 客户端生成工具调用所需的最小契约。

四、Chat Tools 清单:四个工具与端点映射

README 给出了四个工具的完整映射:

工具端点用途
search_foodsPOST /tools/search_foods按产品名称搜索
lookup_barcodePOST /tools/lookup_barcode查询单个条形码
compare_foodsPOST /tools/compare_foods最多对比五个条形码
check_allergensPOST /tools/check_allergens对某条形码或首个搜索结果核查需避开的过敏原

从 main.py 源码看,四个端点都遵循统一的 FastAPI 实现模式:先解析 JSON 请求体(_json_body),参数不合法时返回success=FalseChatToolResponse;成功后统一返回{success, message, data}结构。各工具的参数契约如下:

  • search_foodsquery(必填,字符串)+page_size(可选整数,默认 5,上限 10);
  • lookup_barcodebarcode(必填,字符串,可含包装上的数字或整串条形码);
  • compare_foodsbarcodes(必填,字符串数组,最多取前 5 个);
  • check_allergensavoid(必填,字符串数组,如["milk", "peanuts", "gluten"]),外加可选的barcodequery用于定位产品。

典型对话示例

README 提供了四条可直接体验的示例提示词:

  • “Look up barcode 737628064502.”
  • “Find oat milk and show sugar per 100g.”
  • “Compare these cereal barcodes.”
  • “Does this snack list milk, peanuts, or gluten?”

这些提示词分别对应lookup_barcodesearch_foodscompare_foodscheck_allergens,Omi 会根据工具描述自动路由到对应端点。

五、源码级原理:数据口径与实现细节

5.1 字段白名单:PRODUCT_FIELDS

main.py 中定义了一个固定字段白名单PRODUCT_FIELDS,包括codeproduct_namegeneric_namebrandsquantitycategories_tagslabels_tagsingredients_textallergens_tagstraces_tagsnutriscore_gradenova_groupecoscore_gradenutrimentsimage_front_small_url。搜索与条形码查询都会通过fields参数把该白名单传给 Open Food Facts API,既缩小了响应体积,也避免把无关大字段灌进聊天上下文。

5.2 营养口径:只认每 100g 数值

_summarize_product()输出的nutrition_per_100g只包含能量(kcal)、脂肪、饱和脂肪、碳水化合物、糖、膳食纤维、蛋白质与盐这 8 个维度。关键在于_nutrient()的实现:它只读取带_100g后缀的键(如fat_100g),并明确拒绝回退到无后缀键——因为无后缀的nutriments键的基准可能是“每份”,若将其误标为 per-100g 就是错误数据。这一口径在 test_nutrient_basis.py 中有专门测试守护:当某产品只有fat(每份)而无fat_100g时,nutrition_per_100g.fat_g必须返回None而非泄漏的每份数值。

5.3 搜索端点:为什么用/cgi/search.pl

源码注释明确写道:/cgi/search.pl才是 Open Food Facts 的全文产品搜索端点,而/api/v2/search不是。因此_search_foods()调用GET /cgi/search.pl,携带action=processsearch_termssearch_simple=1json=1page_sizefields参数。test_search_endpoint.py 通过 monkeypatch 捕获实际路径与参数,断言请求必须落在/cgi/search.pljson=1,从测试层面固定了这一契约。

5.4 过敏原匹配:否定词的“反误报”处理

check_allergens的匹配逻辑在_ingredient_mentions_term()中实现,它对配料文本做小写归一化后使用词边界正则匹配,并专门处理两类否定表达以避免误报:

  • 跳过-free/free后缀(如gluten-free不命中 gluten);
  • 跳过no/non-前缀(如no peanutsnon-dairy不命中相应词)。

最终返回的data中包含matches(命中的回避词)、checked_sources["allergens", "traces", "ingredients text"])与data_note,并生成可读消息,如"{name} may include: milk, peanuts.";未命中时则提示用户仍需核对包装标签。

5.5 网络层:超时、UA 与容错

所有上游请求都通过_openfoodfacts_get()发出:设置Accept: application/json与自定义 User-Agent,超时默认 8 秒(由环境变量控制);遇到网络异常或非 JSON 响应时返回{"error": ...},而不是抛出异常,保证聊天工具稳定返回结构化失败信息。为避免阻塞事件循环,异步端点经run_in_threadpool把同步 requests 调用放入线程池执行。

六、环境变量:全部可调项一览

变量默认值说明
OPENFOODFACTS_BASE_URLhttps://world.openfoodfacts.org生产环境 API 根地址;staging 可切换为https://world.openfoodfacts.net
OPENFOODFACTS_USER_AGENTOmiOpenFoodFactsApp/1.0(附项目仓库地址作为身份标识)Open Food Facts 要求应用自报身份,便于数据贡献者联系
OPENFOODFACTS_TIMEOUT_SECONDS8每个上游请求的超时秒数,可调大以适配慢网络
PORT8080服务监听端口,Railway 与本地运行均使用

在 main.py 中,前三项在模块加载时通过os.getenv读取,BASE_URL还会执行rstrip("/")以兼容带尾斜杠的配置值;PORT则用于uvicorn.run的端口绑定。需要注意:切换BASE_URL到 staging 域时,/cgi/search.pl/api/v2/product/{code}.json的相对路径均保持不变,只是根域名不同。

七、本地开发与验证

README 给出的本地启动方式为:

pip install -r requirements.txt uvicorn main:app --reload --port 8080

依赖锁定在 requirements.txt:fastapi==0.104.1uvicorn==0.24.0requests==2.34.2pydantic==2.5.2。服务启动后,打开:

http://localhost:8080/.well-known/omi-tools.json

即可检查工具清单是否正常返回。此外还可验证:

curl http://localhost:8080/health

健康检查端点返回{"status": "ok", "service": "omi-openfoodfacts-app"}。本地调试单个工具时,例如搜索“燕麦奶”:

curl -X POST http://localhost:8080/tools/search_foods \ -H "Content-Type: application/json" \ -d '{"query": "oat milk", "page_size": 3}'

响应遵循ChatToolResponse结构,形如:

{ "success": true, "message": "Found 3 product(s) for 'oat milk'.", "data": { "query": "oat milk", "count": 42, "products": [ { "barcode": "737628064502", "name": "Oat Milk", "brands": "ExampleBrand", "quantity": "1 L", "nutri_score": "B", "nova_group": "2", "eco_score": "A", "allergens": ["gluten"], "traces": ["sesame seeds"], "labels": ["vegan", "organic"], "categories": ["plant milks", "oat milks"], "ingredients": "Water, oats...", "nutrition_per_100g": { "energy_kcal": 48, "fat_g": 1.5, "saturated_fat_g": 0.2, "carbohydrates_g": 7.0, "sugars_g": 4.0, "fiber_g": 0.8, "proteins_g": 1.0, "salt_g": 0.1 }, "image_url": "https://.../front_small.jpg", "data_note": "Open Food Facts data is community contributed and can be incomplete." } ], "data_note": "Search is capped by this app to reduce API load." } }

八、部署到 Railway

仓库已为 Railway 一键部署备好全部配置:

  • railway.toml:使用nixpacks构建;启动命令为uvicorn main:app --host 0.0.0.0 --port $PORT;健康检查指向/health(超时 100 秒);失败时自动重启,最多重试 3 次;
  • Procfileweb: uvicorn main:app --host 0.0.0.0 --port $PORT,兼容依赖 Procfile 的平台;
  • runtime.txt:指定 Python 版本python-3.11

部署成功后,将railway.toml中的启动命令与 README 中 Omi App 配置表里的Chat Tools Manifest URL对应起来——把YOUR-APP.up.railway.app换成真实域名,填入 Omi 控制台即可完成接入。

九、数据边界与防滥用设计(重要提示)

README 明确强调两点数据注意,这也是使用本插件时必须传达给用户的边界:

  1. 社区贡献数据的天然局限:Open Food Facts 是社区贡献数据库,某产品缺失过敏原、营养字段或配料清单,并不证明该产品安全或完整。因此插件在工具响应中内置了data_note提示(如"Open Food Facts data is community contributed and can be incomplete.""Missing Open Food Facts allergen data does not prove the food is safe."),避免 Omi 把不完整数据包装成“保证”。

  2. 搜索限流:搜索工具将page_size上限固定在 10,防止聊天使用演变成高并发搜索客户端。此外 main.py 的_safe_int()page_size做了1~10的钳制,compare_foods_collect_foods_from_body()也只遍历barcodes[:5],从源头限制了单次请求对上游的冲击。

十、小结

omi-openfoodfacts-app是 Omi 插件体系“独立部署 chat tools 服务”的典型范例:以一份.well-known/omi-tools.json清单声明四个只读工具,用不超过 10 个环境变量的配置即可完成从本地到生产的一致性运行。它同时示范了两个值得复用的工程实践——只取_100g营养键的严格数据口径带否定词过滤的配料匹配算法,两者均有仓库内测试用例背书,可作为后续任何面向消费者数据的聊天工具的设计参考。更多插件体系说明可参见 plugins/README.md,插件共享模型定义见 omi-plugin-sdk/README.md。

【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询