Mealie 食材解析器深度指南:CRF 模型、tokenization 与自定义解析器注册

发布时间:2026/9/15 18:37:24
Mealie 食材解析器深度指南:CRF 模型、tokenization 与自定义解析器注册 Mealie 食材解析器深度指南CRF 模型、tokenization 与自定义解析器注册【免费下载链接】mealieMealie is a self hosted recipe manager and meal planner with a RestAPI backend and a reactive frontend application built in Vue for a pleasant user experience for the whole family. Easily add recipes into your database by providing the url and mealie will automatically import the relevant data or add a family recipe with the UI editor项目地址: https://gitcode.com/GitHub_Trending/me/mealieMealie 使用条件随机场Conditional Random FieldsCRF作为食材ingredient解析与处理的核心模型将一段自由文本如2 tsp minced cilantro, leaves and stems拆解为数量、单位、食材名与备注等结构化字段。本文以 ingredient-parser.md 为骨架结合当前仓库中mealie/services/parser_services/的源码实现与单元测试系统讲解 Mealie 食材解析器的架构演进、tokenization 改进方法、本地测试方式以及如何通过ABCIngredientParser接口注册全新的解析策略。Mealie 如何解析食材CRF 模型与数据来源Mealie 的食材解析并非依赖简单正则而是建立在统计机器学习模型之上。文档明确指出Mealie uses Conditional Random Fields (CRFs) for parsing and processing ingredients.CRF 是一种判别式概率模型常用于序列标注任务。在食材解析场景中模型需要对输入字符串的每个 token 打上数量 / 单位 / 食材 / 备注等标签CRF 能够利用相邻 token 之间的上下文依赖关系从而比独立分类更准确地完成切分。模型的训练数据来自纽约时报New York Times编译的超过 10 万条食材数据集。文档同时强调了一个重要观点既然模型已覆盖绝大多数常见食材形态盲目增加训练数据未必能有效提升解析效果——真正的改进瓶颈往往在输入字符串的 tokenization 与预处理环节。这一判断也解释了为何仓库源码中绝大部分解析相关工作都集中在字符串规整、分数转换、括号移动等预处理函数上。从当前仓库的依赖看这一 CRF 能力由第三方库ingredient-parser-nlp提供见 pyproject.toml 中的ingredient-parser-nlp2.7.0锁定版本Mealie 在 NLPParser 中封装调用并围绕它构建了数据匹配、置信度计算与单位标准化等完整链路。文档写作时提到的crfpp代码目录在当前的代码结构中已被重构为parser_utils、brute与NLPParser的组合下文将按当前仓库的实际结构展开。解析器统一抽象ABCIngredientParser 接口Mealie 将解析一段食材文本抽象为统一的接口定义在 mealie/services/parser_services/_base.py 中class ABCIngredientParser(ABC): abstractmethod async def parse_one(self, ingredient_string: str) - ParsedIngredient: ... abstractmethod async def parse(self, ingredients: list[str]) - list[ParsedIngredient]: ...只需实现这两个方法即可成为 Mealie 认可的食材解析器。构造函数接收group_id租户隔离、session数据库会话与translator多语言翻译器基类会基于这些参数构建一个DataMatcher数据匹配器。DataMatcher解析结果与数据库数据的桥接解析器产出的食物名、单位名是自由文本而 Mealie 数据库中存在一组已维护的食材foods与单位units并支持别名、复数形式。DataMatchermealie/services/parser_services/_base.py负责将解析文本与数据库记录对齐精确别名匹配将数据库中食物名、复数名、别名以及单位名、复数名、缩写、复数缩写全部规范化后建立索引foods_by_alias、units_by_alias模糊匹配借助find_match进行模糊查找默认阈值分别为食物 85、单位 70可在ABCIngredientParser的food_fuzzy_match_threshold/unit_fuzzy_match_threshold属性中调整错误纠正find_ingredient_match还会处理一种常见误判——解析器把单位食材整体当成了食材名例如解析出foodlarge onion, unitlarge时会用{unit} {food}重新匹配一次完整食物同时将空字符串的 food/unit 归置为None。也就是说最终写回菜谱的食材条目不是解析器的裸文本而是与用户自己维护的食材库对齐后的记录——这正是 Mealie 能够统一管理食材库、单位体系的基础。解析器注册表与运行时选择解析策略通过RegisteredParser枚举标识见 mealie/schema/recipe/recipe_ingredient.pyclass RegisteredParser(enum.StrEnum): nlp nlp brute brute openai openaiget_parsermealie/services/parser_services/ingredient_parser.py充当工厂函数默认从__registrar字典按枚举取值nlp → NLPParser、brute → BruteForceParser而openai则惰性导入OpenAIParser。因此注册新的解析策略本质上就是实现ABCIngredientParser→ 在__registrar中登记 → 前端通过请求体中的parser字段切换。相关请求模型IngredientsRequest/IngredientRequest同样携带parser: RegisteredParser RegisteredParser.nlp字段mealie/schema/recipe/recipe_ingredient.py默认使用 NLP 解析器。三种解析策略的实现细节NLPParser基于 CRF 的默认解析器NLPParser是 Mealie 的主力解析器核心流程如下mealie/services/parser_services/ingredient_parser.py将数据库中的单位含复数名、缩写、复数缩写组装为custom_units字典注入parse_ingredient调用使 CRF 模型能够感知当前用户单位库调用parse_ingredient(ingredient_string, custom_unitsdatabase_units)得到结构化结果_convert_ingredient将库返回的 amount / name / size / preparation / comment / purpose 逐字段映射为 Mealie 的RecipeIngredient并计算各字段置信度与平均值IngredientConfidence。值得注意的两个细节复合数量当输入出现1 1/2 cups这类混合分数时库返回CompositeIngredientAmount_extract_amount只取第一段作为主数量其余段如(1 cup plus 1 tablespoon)转为备注备选食材substitutionsstock or broth这类或表达会被解析为多个名称_convert_extra_ingredients将备选项转换为RecipeIngredientSubstitution若备选项能匹配数据库中的食物则记录substitute_food_id否则以纯文本note兜底——因为备选字段保存的是食物 ID无法解析为现存食物 ID 的文本若被丢弃就永久丢失了。对应的行为由测试 test_nlp_parser.py 覆盖。BruteForceParser无需模型的暴力解析BruteForceParser面向简单、规整的文本形态通过纯规则切分 token 完成解析mealie/services/parser_services/brute/process.py首 token 尝试按数量解析支持2 1/2混合分数、Unicode 分子符、千分位逗号等数量后紧跟单位借助括号配对逻辑区分食材与备注例如mango chunks, (2 large mangoes) (fresh or frozen)中的括号内容被识别为备注当首 token 无法解析为数量时如a stalk bell peppers退化为在前三个 token 中查找数据库单位复用data_matcher.find_unit_match命中后再切分食材与备注。由于不依赖模型与外部库暴力解析器适合需要离线、确定性结果的场景也常被用作 OpenAI 解析器的数量置信度对照基准见_calculate_qty_confmealie/services/parser_services/openai/parser.py。OpenAIParserLLM 驱动的解析OpenAIParser将食材列表以 JSON 形式发给 OpenAI并通过data_matcher.units_by_alias将用户单位库注入提示词指导模型识别单位mealie/services/parser_services/openai/parser.py。返回结果经过_convert_ingredient映射为ParsedIngredient并分别计算数量、单位、食材、备注的置信度数量置信度通过与暴力解析结果比对获得整体置信度则基于rapidfuzz的 token 排序相似度。该解析器需要配置 AI Provider 后方可使用属于可选增强能力。改进解析效果的核心tokenization 与文本预处理文档强调提升解析效果的主要抓手是改进原始字符串的 tokenization 与解析而不是堆更多训练数据。当前仓库将这类工具集中在mealie/services/parser_services/parser_utils/下共两个模块string_utils.py字符串规整工具mealie/services/parser_services/parser_utils/string_utils.py 提供四类核心函数函数作用典型场景move_parens_to_end用正则将括号内容移动到字符串末尾2 cups mango chunks (fresh or frozen)中的(fresh or frozen)被挪到结尾避免干扰数量/单位的定位remove_footnote_markers移除字符串末尾的星号*脚注标记某些网站用*引用页脚注释不应算作食材名的一部分字符串中间的其他星号保留convert_vulgar_fractions_to_regular_fractions将 Unicode 分数符号½、¾、⅓…共 18 个转换为普通分数文本1½转换为1 1/2特意在分数前补空格避免1½被错误拼成11/2extract_quantity_from_string从字符串头部提取数量支持混合分数、纯分数、整数与小数返回(quantity, remaining_str)供 OpenAI 解析器对比数量时复用unit_utils.py单位标准化与换算mealie/services/parser_services/parser_utils/unit_utils.py 基于pint单位库实现单位换算能力UnitConverter.parse把字符串单位解析为pint.UnitstrictTrue时未知单位抛出UnitNotFound_resolve_ounce处理菜谱中常见的ounce 实为 fluid ounce歧义——当 ounce 与体积单位一同出现时自动视作液量盎司can_convert/convert/merge判断可换算性、换算数量与单位、合并两个数量merge_quantity_and_unit利用数据库单位记录中的standard_quantity/standard_unit标准化字段将两个数量合并并按结果大小自动选择更大或更小的单位展示。brute/process.py 中的 tokenization 细节暴力解析器内部还包含一套完整的 tokenization 策略值得在改进解析器时参考parse_amount按字符逐个吞噬数字、.、,、/后跟数字的序列来提取数量parse_fraction既支持x/y文本分数也支持通过 Unicode 分解unicodedata.decomposition解析单个分数符号parse_ingredient处理括号配对token 以)结尾时回溯寻找(找到则括号内容整体归为备注并从食材中剔除整个 token 序列都被括号包裹时视为错误并抛ValueError触发上层回退逻辑以逗号结尾的 token 作为食材结束的分界其后内容归为备注parse_ingredient_with_comma。这些规则共同保证了2 pounds russet potatoes, peeled, and cut into 3/4-inch cubes这类复杂句式的正确切分对应测试见 test_brute_parser.py。用测试驱动解析器改进文档给出的建议是为解析器注册额外的测试用例在改动 tokenizer 后运行测试验证。原文档指出的测试路径mealie/tests/unit_tests/test_crfpp_parser.py在当前仓库中已演化为按解析器拆分的目录tests/unit_tests/services_tests/ingredient_parser/test_nlp_parser.pyNLP 解析器测试test_brute_parser.py暴力解析器测试test_openai_parser.pyOpenAI 解析器测试conftest.py共享 fixture如unique_local_group_id隔离的数据库会话、预置的食材/单位数据。测试采用 pytest 参数化方式组织用例。以 NLP 解析器为例test_nlp_parser.py每个TestIngredient记录input原文与期望的quantity / unit / food / commentsTestIngredient(½ cup all-purpose flour, 0.5, cup, all-purpose flour, ), TestIngredient(1 ½ teaspoons ground black pepper, 1.5, teaspoon, black pepper, ground), TestIngredient(1 1/2 cups chopped onion , 1.5, cup, onion, chopped), TestIngredient(2 teaspoons salt (to taste) , 2, teaspoon, salt, to taste), TestIngredient(1/2 cup, 0.5, cup, , ),断言语义明确数量用pytest.approx容差比较单位、食材、备注分别与期望值精确比对。暴力解析器的用例还带有可读性极强的id如2 tbsp minced cilantro, leaves and stems便于定位失败用例test_brute_parser.py。此外NLP 解析器还专门测试了信息不丢失约束将解析结果与备选文本用or拼接后通过rapidfuzz.token_sort_ratio与期望文本比对要求相似度不低于 90——确保1 cup fresh basil or 2 tablespoons dried basil这类备选表达在转换后没有任何半句被丢弃test_nlp_parser.py。本地运行测试的环境要求文档特别提醒解析器相关测试不会在 CI 环境中运行因此必须在本机安装 CRF 相关依赖后才能执行测试macOS 用户可通过 Homebrew 安装。在当前仓库中这意味着需要安装ingredient-parser-nlp及其 CRF 模型依赖见 pyproject.toml随后运行pytest tests/unit_tests/services_tests/ingredient_parser/新增用例的方式就是在上述参数化列表中追加TestIngredient或pytest.param再运行对应文件验证改动效果。提交改进 PR 的注意事项鉴于解析器测试不进入 CI评审者无法自动获得回归保障文档明确要求提交改进解析器的 PR 时必须附上三样东西你的测试用例新增的参数化输入与期望输出你试图解决的问题例如某类食材句式解析错误的具体样例改动后的结果改动前后行为对比、对现有用例的影响。缺少这些材料可能导致 PR 因无法验证而延迟合并。这也是以测试驱动解析器改进这一开发流程的配套要求。注册全新的解析器文档指出另一种扩展路径是实现ABCIngredientParser接口以注册新的解析策略让用户在做菜谱解析时有更多选项。结合源码完整流程包含四个步骤实现接口新建解析器类继承ABCIngredientParser实现parse_one单条与parse批量两个抽象方法返回ParsedIngredient。可以复用基类的DataMatcher完成结果与数据库食材/单位的对齐参考BruteForceParser与OpenAIParser的写法登记枚举在 mealie/schema/recipe/recipe_ingredient.py 的RegisteredParser中新增枚举值注册工厂在 ingredient_parser.py 的__registrar字典中登记枚举 → 解析器类的映射前端接入解析接口请求体中的parser字段即可切换策略默认nlp。由于__registrar是模块级字典解析策略在运行时即可被选择这正好呼应文档中register additional parsing strategies at runtime的设计意图。相关资源定位文档末尾提到的两个配套项目——预训练模型仓库mealie-nlp-model提供 CRF 训练好的模型文件与CRF forkhay-kot/crfpp文档写作时 Mealie 使用的 CRF 实现——是理解模型底层机制的重要参考。需要说明的是当前仓库已切换到ingredient-parser-nlp库作为 NLP 解析后端因此若想深入训练与模型细节应以该库及其依赖的模型为准而 tokenization 层面的优化仍集中在 parser_utils 与 brute 两个目录是贡献者最值得投入精力的改进区域。小结Mealie 的食材解析器是一个模型 规则 数据匹配三层协作的体系CRF 模型负责从自由文本中抽取语义字段tokenization 工具负责把杂乱文本规整为模型友好的输入DataMatcher负责把解析结果对齐到用户自己的食材库。理解这三层就能明白文档的核心理念——与其给模型喂更多数据不如改进输入文本的预处理与解析规则。对希望贡献解析器能力的开发者而言正确的路径是新增参数化测试用例 → 修改parser_utils或brute中的解析逻辑 → 本地运行pytest验证 → 提交附带用例、问题与结果说明的 PR。而想要引入完全不同的解析技术如基于规则的引擎或新的 LLM 服务ABCIngredientParser接口 __registrar注册表提供了足够低成本的扩展点。【免费下载链接】mealieMealie is a self hosted recipe manager and meal planner with a RestAPI backend and a reactive frontend application built in Vue for a pleasant user experience for the whole family. Easily add recipes into your database by providing the url and mealie will automatically import the relevant data or add a family recipe with the UI editor项目地址: https://gitcode.com/GitHub_Trending/me/mealie创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考