Waterman · 20 小时前 · 辽宁

【“星睿O6”AI PC开发套件评测】基于Qwen-VL的AI烹饪助手

随着大语言模型(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"

安装完成后如下图:
 title=
之后我们使用pip list命令查看当前已安装的python包,可以看到MNN-OPENCL已安装成功。
 title=

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字段包含图像的相关信息,从而实现“看图识菜”的多模态理解。

流式与非流式生成

为了提升交互体验,系统提供两种生成模式:

  1. 流式生成
    流式模式能够在前端呈现“打字机效果”,具体实现如下:

    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
    
  2. 非流式生成
    在批量调用或后台任务场景中,可使用非流式生成接口:

    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()  # 换行
    

     title=

    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()
    

    当用户输入问题或上传图片后,系统的执行流程如下:
     title=

    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

推荐阅读
关注数
0
文章数
2
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息