开发日志(十):RAG 的智能菜单助手设计
一、项目背景
在境外旅行、跨文化聚餐或者浏览外文菜单时,用户经常会遇到以下问题:
- 菜单使用外语书写,无法快速理解菜品名称和配料;
- 菜单排版复杂,传统 OCR 只能识别文字,难以还原菜品与价格之间的对应关系;
- 用户可能存在过敏原、忌口、素食、低糖等饮食限制;
- 即使完成菜单翻译,用户仍然不知道“哪些菜适合自己”;
- 普通聊天模型不了解当前菜单内容,容易推荐菜单中不存在的菜品。
因此,本项目并没有将功能停留在“图片翻译”阶段,而是设计了一套完整的智能菜单理解与问答系统。
用户上传菜单图片后,系统首先使用多模态大模型识别菜单内容,将图片转换为结构化菜品数据;随后把菜品信息写入向量数据库;当用户针对菜单进行提问时,系统结合菜单知识、用户偏好和大语言模型生成个性化回答。
整个系统形成了如下业务闭环:
菜单图片上传 ↓ 多模态模型识别 ↓ 结构化菜品 JSON ↓ 文本切分与向量化 ↓ Chroma 向量数据库 ↓ 用户问题与偏好 ↓ 相关菜品检索 ↓ 大语言模型生成回答 ↓ Flutter 页面展示这并不是简单地给原有应用增加一个聊天框,而是将“多模态识别、知识入库、向量检索、个性化推荐和移动端交互”整合成一条完整的 AI 工程链路。
二、项目需求分析
1. 菜单图片识别
系统需要支持用户上传真实菜单图片,并从图片中提取:
- 原始菜品名称;
- 中文翻译名称;
- 菜品描述;
- 价格;
- 菜品标签;
- 可能包含的食材;
- 辣度、素食等属性。
传统 OCR 通常只能返回零散文字,例如:
Spicy Chicken 12.99 Beef Noodles 15.50但系统真正需要的是结构化结果:
{"name_original":"Spicy Chicken","name_zh":"香辣鸡肉","description":"Chicken cooked with chili and vegetables","price":"12.99","tags":["辣","鸡肉"]}因此,识别过程不仅包含 OCR,还包含语义理解、字段归类和结构化抽取。
2. 菜单知识问答
识别结果展示完成后,用户还可能继续提出问题,例如:
- 哪一道菜不辣?
- 有适合素食者的菜吗?
- 我对花生过敏,哪些菜不能点?
- 推荐一道价格较低的主食。
- 这几道菜中哪一道热量可能更低?
- 给我推荐两道适合分享的菜。
如果直接将问题发送给普通大模型,模型并不知道当前菜单里有哪些菜,也可能编造菜单中不存在的内容。
因此,系统需要使用 RAG 技术,将当前菜单作为外部知识来源。
3. 用户个性化偏好
不同用户的饮食需求并不相同,系统需要考虑:
- 过敏原;
- 忌口食材;
- 素食或纯素需求;
- 清真饮食需求;
- 辣度偏好;
- 口味偏好;
- 价格倾向。
用户提出“帮我推荐一道菜”时,系统不能只根据菜品名称回答,而应结合用户个人信息进行推荐。
4. 移动端完整交互
Flutter 客户端需要实现完整流程:
选择图片 → 上传图片 → 等待识别 → 展示菜单 → 发起问题 → 等待后端生成 → 展示回答此外还需要处理网络异常、模型异常、空结果、鉴权失效等情况,避免页面白屏或者一直处于加载状态。
三、什么是 RAG
RAG 的全称是 Retrieval-Augmented Generation,即“检索增强生成”。
它的核心思想是:
在大模型回答问题之前,先从外部知识库中检索相关内容,再让模型根据检索结果生成答案。
一个基本的 RAG 流程可以表示为:
用户问题 ↓ 问题向量化 ↓ 向量数据库相似度检索 ↓ 获取相关知识片段 ↓ 知识片段与问题拼接 ↓ 大模型生成回答例如,用户提问:
我不能吃辣,推荐一道主食。系统会先从当前菜单知识库中检索与“不辣”“主食”“推荐”等语义相关的菜品,再将检索结果传给大模型。
最终发送给模型的内容可能类似:
用户偏好: - 不吃辣 - 偏好主食 菜单检索结果: 1. Mushroom Pasta,奶油蘑菇意面,价格 13.99,标签:不辣、主食 2. Spicy Beef Noodles,香辣牛肉面,价格 15.99,标签:辣、主食 用户问题: 我不能吃辣,推荐一道主食。 请只根据菜单检索结果回答。模型便可以推荐奶油蘑菇意面,并说明不推荐香辣牛肉面的原因。
四、为什么不能只使用大模型
直接调用大模型虽然实现简单,但存在几个明显问题。
1. 大模型不了解当前菜单
当前菜单属于用户实时上传的数据,不在模型原有训练知识中。
2. 容易出现幻觉
如果让模型自由回答,它可能生成菜单中不存在的菜品、价格或配料。
3. 菜单内容会不断变化
每次上传的菜单都不同,无法通过固定提示词保存全部菜单信息。
4. 长菜单会占用大量上下文
直接把完整菜单放入 Prompt,会增加 Token 消耗,也可能降低模型对重点信息的关注度。
RAG 可以只检索与当前问题相关的若干菜品,从而减少上下文长度,提高回答相关性。
五、本项目采用的核心技术
1. Qwen 多模态大模型
多模态模型负责理解菜单图片。
与传统 OCR 相比,多模态模型不仅能够识别文字,还可以理解:
- 菜名与价格之间的对应关系;
- 菜品分类;
- 描述和菜名之间的从属关系;
- 菜品可能具有的标签;
- 图片中的版面结构。
模型输出经过约束后转换为统一的 JSON 格式,为后续处理提供基础。
2. LangChain
LangChain 用于组织 RAG 处理流程,包括:
- 将菜品 JSON 转换为 Document;
- 调用 Embedding 模型生成向量;
- 管理检索过程;
- 构造问答 Prompt;
- 调用聊天模型生成答案。
3. Embedding 向量模型
Embedding 模型负责将文本转换为向量。
语义相近的文本在向量空间中的距离通常也更近。例如:
“不辣的菜” “清淡菜品” “没有辣椒的食物”虽然文字不完全相同,但经过向量化后具有较高的语义相似度。
4. Chroma 向量数据库
Chroma 用于存储菜品文本向量和菜品元数据。
系统可以根据用户问题执行相似度搜索,返回最相关的 top-k 个菜品文档。
5. FastAPI
后端使用 FastAPI 提供接口,主要负责:
- 接收菜单图片;
- 调用多模态模型;
- 解析结构化结果;
- 执行向量入库;
- 接收用户问题;
- 查询用户偏好;
- 调用 RAG 问答服务;
- 返回统一格式的数据。
6. Flutter
Flutter 客户端负责:
- 图片选择和上传;
- 菜单结果展示;
- RAG 对话交互;
- 用户偏好传递;
- 收藏和购物车等原有业务;
- 异常提示和加载状态管理。
六、系统的四层架构
第一层:菜单感知层
输入是非结构化菜单图片,输出是结构化菜品 JSON。
这一层的核心任务不是单纯识别文字,而是将图片内容转换为后端可以稳定处理的数据结构。
第二层:知识入库层
结构化菜品需要重新组织成适合语义检索的文本,例如:
菜品原名:Spicy Chicken 中文名称:香辣鸡肉 描述:鸡肉搭配辣椒和蔬菜烹制 价格:12.99 标签:辣、鸡肉、主菜随后生成向量并写入 Chroma 数据库。
第三层:检索增强问答层
用户提问后,系统先检索相关菜品,再结合用户偏好构建 Prompt,最后调用聊天模型回答。
第四层:前后端业务闭环层
Flutter 与 FastAPI 完成接口联调,确保菜单识别结果能够自动入库,并能够在结果页中进行真实问答。
七、项目整体特点
本项目可以概括为一个:
多模态菜单识别 + 向量检索 + 个性化推荐 + 移动端交互闭环的复合式 RAG 系统。
它同时涉及:
- 非结构化图片理解;
- 结构化信息抽取;
- 菜品知识表示;
- 向量索引构建;
- 检索增强生成;
- 用户偏好融合;
- 前后端接口编排;
- 模型运行环境管理;
- 异常处理与工程兜底。
相比普通的聊天问答功能,这类系统的难点不只在模型本身,更在于如何让每个环节真正连接起来。
下一篇文章将详细介绍该系统的具体开发过程,包括菜单处理接口、Document 构造、Chroma 入库、RAG 问答服务和 Flutter 结果页改造。
