随着大语言模型(LLM)的迅速发展,AI 不再只是“会聊天”的工具,而是能够理解、推理并生成多模态内容的智能体。本文将基于Orion O6平台和Qwen-VL模型实现一个完整的AI烹饪助手。它能“看图识菜”,“智能生成食谱”,“结合菜谱知识库检索生成菜谱具体制作步骤”。
环境准备
依赖包安装
首先安装用到的python依赖包,其中rank-bm25 sentence-transformers faiss-cpu主要用于向量检索,gradio则用于 Web 界面展示:
pip install pydantic -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install rank-bm25 sentence-transformers faiss-cpu -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install gradio -i https://pypi.tuna.tsinghua.edu.cn/simple
embedding模型下载
因为RAG需要用到embedding模型,但是默认直接从huggingface下载,若网络环境则会下载失败。因此我们先从modelscope下载模型到本地,并在使用时直接使用本地路径。
sudo apt-get install git-lfs
git lfs install
git clone https://www.modelscope.cn/BAAI/bge-small-zh-v1.5.git
一定要确保安装了git-lfs,否则模型权重文件将下载不完整,并报出如下错误:
Error while deserializing header: header too large
pymmn编译
由于模型基于mnn运行,如果想使用opencl作为后端,需要我们基于源码进行python包的编译,具体命令如下:
cd MNN/pymnn/pip_package
sudo apt install python3-dev
python3 build_deps.py "opencl,llm,openmp"
pip uninstall MNN MNN-OPENCL -y
python3 setup.py install --version 0.1.0 --deps "opencl,llm,openmp"
安装完成后如下图:
之后我们使用pip list命令查看当前已安装的python包,可以看到MNN-OPENCL已安装成功。
Qwen2.5-VL 模型封装
我们首先基于 MNN推理引擎 对 Qwen2.5-VL-7B-Instruct 模型进行封装的实现方案。该方案通过 MNN 提供的 llm 与 cv 模块,构建了一个简洁、可复用的 Python 接口类 ChatQwen2\_5\_VL,支持纯文本与图文混合输入,并提供流式(streaming)与非流式(batch)两种生成模式。
初始化与模型加载
我们基于MNN的llm模块封装了一个简洁的Python类ChatQwen2\_5\_VL,支持纯文本与图文混合输入。初始化部分如下:
def __init__(
self,
model_name: str = "Qwen/Qwen2.5-VL-7B-Instruct",
max_new_tokens: int = 1024,
):
self.model_name = model_name
self.max_new_tokens = max_new_tokens
self.model = llm.create(self.model_name)
self.model.load()
模型加载完成后,即可在 CPU 或 OpenCL 后端执行推理。
Prompt构建
Qwen-VL 支持图像与文本输入,因此在输入构造阶段,我们根据是否存在图片路径决定输入格式:
def _gen_prompt(self, text, img_path=None):
if img_path:
img = cv.imread(img_path)
prompt = {
'text': '<img>image_0</img>' + text,
'images': [{'data': img, 'height': 420, 'width': 420}]
}
else:
prompt = {'text': text}
prompt['text'] = self.model.apply_chat_template(prompt['text'])
return prompt
其中text字段就是输入给大模型的文本内容。images字段包含图像的相关信息,从而实现“看图识菜”的多模态理解。
流式与非流式生成
为了提升交互体验,系统提供两种生成模式:
流式生成
流式模式能够在前端呈现“打字机效果”,具体实现如下:def stream(self, text, img_path=None): prompt = self._gen_prompt(text, img_path) token = self.model.tokenizer_encode(prompt) self.model.generate_init() for i in range(self.max_new_tokens): logits = self.model.forward(token) token = np.argmax(logits) self.model.context.current_token = token try: word = self.model.tokenizer_decode(token) except UnicodeDecodeError: word = "" if self.model.stoped(): break yield word非流式生成
在批量调用或后台任务场景中,可使用非流式生成接口:def generate(self, text, img_path=None): prompt = self._gen_prompt(text, img_path) token = self.model.tokenizer_encode(prompt) self.model.generate_init() generated_text = "" for i in range(self.max_new_tokens): logits = self.model.forward(token) token = np.argmax(logits) self.model.context.current_token = token word = self.model.tokenizer_decode(token) if self.model.stoped(): break generated_text += word return generated_text使用示例
if __name__ == "__main__": chat = ChatQwen2_5_VL( model_name="../../models/Qwen3-VL-4B-Instruct-mnn/config.json" ) img_path = '../../MNN/resource/images/cat.jpg' text = "介绍一下这张图" # image_path = "/home/radxa/Downloads/guobaorou.jpg" # text = "图片上是什么菜?请给出菜名。" # 流式输出(打字机效果) print("AI: ", end="") # chat._stream(prompt) for chunk in chat.stream(text): print(chunk, end="", flush=True) print() # 换行RAG
在检索增强生成(Retrieval-Augmented Generation, RAG)架构中,我们创建了一个RAGProcessor 类,它是系统的知识增强核心,用于根据用户的输入在海量菜谱知识库中检索相关的菜谱内容。
模块结构与初始化
class RAGProcessor: """Handles RAG processing for recipe retrieval.""" def __init__(self, csv_path=None, db_path=None): self.json_path = csv_path or "./db/recipes_train_dataset.json" self.db_path = db_path or "./db/chat_history.db" self.model = None self.bm25 = None self.index = None self.user_inputs = [] self.assistant_responses = []向量生成与索引初始化
首先是调用embedding模型,将菜谱内容编码为高维向量:
self.embeddings = self.model.encode(self.user_inputs, convert_to_numpy=True) self.index = faiss.IndexFlatL2(self.embeddings.shape[1]) self.index.add(self.embeddings) print(f"FAISS索引初始化成功,维度: {self.embeddings.shape[1]}")在 BM25 初始化阶段,通过简单的分词生成倒排索引:
tokenized_corpus = [doc.split(" ") for doc in self.user_inputs] self.bm25 = BM25Okapi(tokenized_corpus)BM25 提供词频特征的精确匹配,而 FAISS 负责捕捉语义相似性,两者结合后实现语义增强搜索。
混合检索策略
在检索阶段,我们结合了 BM25 稀疏检索 和 FAISS 稠密检索 实现了两阶段检索策略。核心检索逻辑如下:
def search_recipes(self, query, top_k=5): tokenized_query = query.split(" ") bm25_scores = self.bm25.get_scores(tokenized_query) top_n_indices_bm25 = np.argsort(bm25_scores)[-top_k:][::-1] query_embedding = self.model.encode([query], convert_to_numpy=True) distances, indices = self.index.search(query_embedding, top_k)首先基于bm25检索获取top\_k条数据,之后使用稠密检索计算余弦距离,并最终排序输出最相关的对话内容,为 LLM 的生成阶段提供上下文支撑:
combined_scores[idx] += 1 / (1 + distances[0][i]) # 将距离转化为相似度得分 sorted_results = sorted(combined_scores.items(), key=lambda x: x[1], reverse=True)对话逻辑管理
为了整合大模型推理与RAG模块,我们创建了一个SimpleChatManager 类,用于负责协调推理与检索逻辑:
class SimpleChatManager: def __init__(self, model_path, max_new_tokens=1024): self.chat_model = ChatQwen2_5_VL(model_name=model_path) self.rag_processor = RAGProcessor()当用户输入问题或上传图片后,系统的执行流程如下:
RAG 内容增强
我们编写了如下函数,从而将检索到的相关菜谱整合为提示信息,输入给大模型进行处理:
def _enhance_messages_with_rag(self, query, search_results): reference_info0 = "以下是相关的菜谱参考信息:\n\n" reference_info = "" for i, result in enumerate(search_results[:3], 1): user_question = result.get("user_input", "") recipe_content = result.get("assistant_response", "") score = result.get("score", 0) if score > 0.5: reference_info += f"参考 {i}:\n问题:{user_question}\n回答:{recipe_content}\n相关度:{score:.3f}\n\n" reference_info += "请基于以上参考信息,结合你的烹饪知识,为用户提供详细准确的菜谱制作方法、食材清单和步骤说明。\n" return reference_info0 + reference_info + query这种方式能显著减少模型幻觉,使生成内容更贴近真实烹饪知识。
内容生成
内容生成部分代码如下:
def stream_response(self, text, img_path=None): if img_path != None: query = self.chat_model.generate("图片上是什么菜?仅给出菜名即可。", img_path) + text else: query = text # print(query) # 检查RAG处理器是否可用 if self.rag_processor is None: print("RAG处理器不可用,使用直接模型调用") yield from self._stream_without_rag() return # 1. 根据用户输入进行检索 search_results = self.rag_processor.search_recipes(query, top_k=3) # 2. 将检索结果添加到消息中 if search_results != None: enhanced_messages = self._enhance_messages_with_rag(query, search_results) else: enhanced_messages = text # enhanced_messages = "结合你的烹饪知识,为用户提供详细准确的菜谱制作方法、食材清单和步骤说明。\n" + text # print("enhanced_messages: ", enhanced_messages) try: # 3. 流式调用模型生成回复 yield from self.chat_model.stream(enhanced_messages) # yield chunk except Exception as e: print(f"RAG处理失败: {e},回退到直接模型调用") yield from self._stream_without_rag(query) def _stream_without_rag(self, text): """ 不需要RAG的流式处理路径 Yields: str: 流式文本片段 """ try: for chunk in self.chat_model.stream(text): yield chunk.content except Exception as e: yield f"抱歉,处理您的请求时出现了问题:{str(e)}"Gradio 可视化界面
最后,我们通过 Gradio 构建了一个可交互的 Web 端界面,实现人机对话、图像上传与实时推理。
文件上传
def handle_file_upload(file): allowed_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'} file_ext = os.path.splitext(file.name)[1].lower() if file_ext not in allowed_extensions: error_msg = f"不支持的文件类型 {file_ext}" return None, gr.update(value=error_msg, visible=True) success_msg = f"图片上传成功:{os.path.basename(file.name)}" return file.name, gr.update(value=success_msg, visible=True)主界面布局
with gr.Blocks(css=simple_css, title="我不是厨神") as demo: # 简洁的标题 gr.Markdown("# 我不是厨神", elem_classes="main-title") gr.Markdown("AI驱动的智能烹饪助手 - 让人人都能成为家庭大厨", elem_classes="subtitle") # 项目亮点展示 with gr.Row(): with gr.Column(scale=1): gr.Markdown(""" <div align="center"> **智能识别** 拍照识菜,秒懂制法 </div> """) with gr.Column(scale=1): gr.Markdown(""" <div align="center"> **海量菜谱** RAG检索万千食谱 </div> """) with gr.Column(scale=1): gr.Markdown(""" <div align="center"> **意图理解** 精准解答烹饪疑问 </div> """) # 聊天界面 chatbot = gr.Chatbot( label='对话', # height=DEFAULT_HEIGHT, elem_classes="chat-container", show_label=True, render_markdown=True # 启用Markdown渲染 ) # 输入区域 with gr.Row(): query = gr.Textbox( lines=2, label='输入您的问题', placeholder="例如:红烧肉怎么做?", scale=4 ) with gr.Column(scale=1): submit_btn = gr.Button("发送", scale=1, variant="primary") empty_bin = gr.Button("清除历史", variant="secondary") # 上传和状态 with gr.Row(): with gr.Column(scale=1): addfile_btn = gr.UploadButton( "上传图片", file_types=[".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"], variant="secondary" ) status_display = gr.Textbox( label="状态", visible=False, interactive=False ) with gr.Column(scale=1): image_display = gr.Image( label="已上传的图片", height=200, show_label=True, visible=False )系统支持实时聊天、菜谱检索与图像识别,界面简洁自然,交互逻辑清晰。
小结
本次基于Orion O6开发板,在本地实现了一个能够“看图识菜、检索菜谱、智能生成烹饪步骤”的 AI 烹饪助手。系统基于 Qwen-VL 多模态模型,结合 RAG 检索增强,让模型既能理解图像内容,又能利用菜谱知识库给出详细的制作步骤。
演示视频如下:
https://www.bilibili.com/video/BV1XRykBDEb3/?aid=null&cid=null&page=1