【AI大模型实战】Qwen3+QVQ-Max实现一个能吃图片的RAG,建议收藏!!

前言

在之前我制作了几个 RAG,但都是吃文字吐文字的。一想到我的 RAG 只能看到冰冷的文字,而看不到类似猫娘的图片时,我就替 AI 感到惋惜。

那么本文就手把手制作一个能吃图片的 RAG,打造一个能够进行图像检索的 RAG。

图片

既然可以检索图片,那么不失一般性地,我们可以用这玩意挑选符合我们要求的猫娘(doge)。

先来看一看最后的效果,我制作了页面。首先,我已经把这些猫娘的图片上传到系统里面了。

图片

我现在描述一下我喜欢的猫娘类型,之后点击“检索并回答”。

图片

它返回的结果如下:(原图有点长)

图片

这个还真的很符合咱们的要求咧(手动傲娇)。上面这个图片太长了,猫娘上方的文字回复如下所示:

图片

为了避免偶然性,提高准确性,我们再测试一番:

图片

结果如下:

图片

图片

都说了大模型看人很准吧(叉腰)。

但是当我们尝试问一些与所有图片都无关的内容时:

图片

我们会直接调用 Qwen3-235b-a22b 进行回答。

图片

01、涉及的一些主要模型

(1)将图片转化为向量表示

为了判断一张图片是否与用户的提问匹配,我们会把图片和提问都映射到同一个“语义空间”中,得到各自的向量表示。

在这个空间里,语义相近的内容对应的向量距离会更小,然后,只需用常见的相似度度量(例如余弦相似度)来计算两者向量的相似度,就能快速评估提问与图片之间的匹配程度。

图片

能够同时处理文字和图片,我们得找一个多模态的向量嵌入模型,我们选择 cohere 的 embed-v4.0 模型。

Cohere 的 embed-v4.0 是一款面向企业级检索和智能代理的多模态嵌入模型,能够直接对文本、图片以及混合的文档(如 PDF 页面)生成高质量向量表示,适合构建大规模、低延迟的语义检索和 RAG 系统。

(2)本项目执行流程图

一开始,用户提问,用户的提问会被 embed-v4.0 模型编码为向量表示,(图库中的图片均已经被 embed-v4.0 编码为向量表示),将用户的提问与图库中的所有图片(的向量)作点积。

如果所有点积小于相似度阈值(0.3),则直接调用 qwen3-235b-a22b 进行文字回复,如果有点积大于相似度阈值(0.3),则选取点积最大所对应的图片,将图片传入 qvq-max-2025-03-25 进行回复。

qwen3-235b-a22b 能吃文字,如果这个模型是多模态的该多好。

qvq-max-2025-03-25 能吃图片:

图片

02、代码讲解

https://github.com/mindsRiverPonder/LLM-practice/tree/main/image-RAG%20for%20catgirl

(1)下载并引入必要的库

其中 streamlit 是用来构建可视化页面的:

!pip install -q cohere streamlit

引入必要的库:

import osimport ioimport timeimport zipfile           #解压缩用的import base64import requestsimport numpy as npimport PIL.Imageimport streamlit as stfrom openai import OpenAIimport cohere

(2)配置可视化页面的 css 样式(可跳过)

css 样式你可以选择自己设计(强烈推荐),让画面变得更好看。我就不摆出我的 css 样式设计了,毕竟是依托勾式。

st.markdown(    """    <style>    自己设计哈</style>    """ , unsafe_allow_html=True)

(3)配置对于的 API-key,初始化客户端

为了使用图像嵌入模型 embed-v4.0,你需要先去 cohere 官网申请一个免费的 API-KEY(那肯定是有速率限制的)。

https://dashboard.cohere.com/api-keys

为了使用 qwen3-235b-a22b 和 qvq-max-2025-03-25,你需要先去阿里云百炼官网申请一个 API-KEY。

https://bailian.console.aliyun.com/?tab=model#/model-market
COHERE_API_KEY = os.getenv("COHERE_API_KEY", "你的API-KEY")DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY", "你的API-KEY")EMBED_MODEL = "embed-v4.0"QUERY_MODEL = "qwen3-235b-a22b"VISION_MODEL = "qvq-max-2025-03-25"def init_clients():    co = cohere.ClientV2(api_key=COHERE_API_KEY)    qwen_client = OpenAI(        api_key=DASHSCOPE_API_KEY,        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"    )    return co, qwen_clientco, qwen_client = init_clients()

(4)定义一些必要的工具函数

这个相似度阈值,你们可以根据需要进行调整,调高一些就代表检索图片更严格。

MAX_PIXELS = 1568 * 1568 #图片最大像素SIM_THRESHOLD = 0.3  # 相似度阈值,可根据需要调整

1.调整图像尺寸,当超过最大像素时自动缩小。

def resize_image(pil_image: PIL.Image.Image):    w, h = pil_image.size    if w * h > MAX_PIXELS:        scale = (MAX_PIXELS / (w * h)) ** 0.5        pil_image.thumbnail((int(w * scale), int(h * scale)))


2.将图像转化为 Base64 编码,很多 API 都要求图像以 Base64 编码的字符串形式传输。

def base64_from_pil(pil_image: PIL.Image.Image) -> str:    fmt = pil_image.format or "PNG"    resize_image(pil_image)    with io.BytesIO() as buf:        pil_image.save(buf, format=fmt)        data = base64.b64encode(buf.getvalue()).decode()    return f"data:image/{fmt.lower()};base64,{data}"


3.通过 embed-v4.0 生成图像的嵌入向量,并且归一化向量,便于计算余弦相似度。

def embed_document(img: PIL.Image.Image):    b64 = base64_from_pil(img)    doc_in = {"content": [{"type": "image", "image": b64}]}    resp = co.embed(model=EMBED_MODEL,                    input_type="search_document",                    embedding_types=["float"],                    inputs=[doc_in])    vec = np.array(resp.embeddings.float[0])    return vec / np.linalg.norm(vec)   #归一化


4.将用户的输入也映射为向量,便于检索相关图片

def embed_query(text: str):    resp = co.embed(model=EMBED_MODEL,                    input_type="search_query",                    embedding_types=["float"],                    texts=[text])    vec = np.array(resp.embeddings.float[0])    return vec / np.linalg.norm(vec)


(5)模型生成回复

因为我这里用的是 qwen3-235b-a22b 和 qvq-max-2025-03-25,他们支持流式输出,我在页面上也流式展示回复的内容。

def generate_answer(question: str, img: PIL.Image.Image = None):    """流式输出"""    if img is not None:        b64_image = base64_from_pil(img)          messages = [            {"role": "system", "content": "根据下面的图片回答问题。"},             {            "role": "user",            "content": [                {"type": "text", "text": question},                {"type": "image_url", "image_url": {"url": b64_image}}            ]        }]        response = qwen_client.chat.completions.create(            model=VISION_MODEL,            messages=messages,            stream=True,  # 启用流式输出        )    else:        # 处理纯文本输入        messages = [            {"role": "system", "content": "请仅根据文字问题回答"},            {"role": "user", "content": question}        ]        response = qwen_client.chat.completions.create(            model=QUERY_MODEL,            messages=messages,            stream=True,  # 启用流式输出        )    def stream_response():        try:            for chunk in response:                if chunk.choices[0].delta.content:                    yield chunk.choices[0].delta.content        except Exception as e:            st.error(f"流式输出错误: {str(e)}")            yield "抱歉,流式输出过程中发生错误。"    return stream_response()


(6)定义存储上传图片路径和对应的嵌入向量的地方

if 'img_paths' not in st.session_state:    st.session_state.img_paths = []if 'doc_embeddings' not in st.session_state:    st.session_state.doc_embeddings = []


(7)页面搭建

st.sidebar.title("导航栏")
page = st.sidebar.radio("功能", ["查询", "上传", "图库"], index=0)
if page == "上传":
    st.header("上传图片资源")
    uploaded = st.file_uploader("上传单张图片", type=['png','jpg','jpeg'])    
    url = st.text_input("或输入图片 URL")    
    zipf = st.file_uploader("上传 ZIP 压缩包 (仅图片)", type=['zip'])    
	if uploaded:
	        img = PIL.Image.open(uploaded)        
	        path = f"uploaded_{int(time.time())}.png"        
	        img.save(path)        
	        st.session_state.img_paths.append(path)        
	        st.session_state.doc_embeddings.append(embed_document(img))        
	        st.success(f"已添加: {path}")    
	        if url:        
	        try:            
	        r = requests.get(url)            
	        r.raise_for_status()            img = PIL.Image.open(io.BytesIO(r.content))            path = f"url_{int(time.time())}.png"            img.save(path)            st.session_state.img_paths.append(path)            st.session_state.doc_embeddings.append(embed_document(img))            st.success(f"已下载并添加成功: {path}")        except Exception as e:            st.error(f"URL 下载失败 : {e}")    if zipf:        with zipfile.ZipFile(zipf) as z:            for fname in z.namelist():                if fname.lower().endswith(('.png','.jpg','.jpeg')):                    data = z.read(fname)                    img = PIL.Image.open(io.BytesIO(data))                    path = f"zip_{int(time.time())}_{os.path.basename(fname)}"                    img.save(path)                    st.session_state.img_paths.append(path)                    st.session_state.doc_embeddings.append(embed_document(img))            st.success("ZIP 中的所有图片已添加")elif page == "查询":    st.header("  图像RAG-文字检索图片")    question = st.text_input("请输入您的问题✍️:")    if st.button("检索并回答"):        if not question:            st.warning("请输入问题后再检索  ")        else:            q_emb = embed_query(question)            if st.session_state.doc_embeddings:                docs = np.vstack(st.session_state.doc_embeddings)                sims = docs.dot(q_emb)                max_sim = float(np.max(sims))                idx = int(np.argmax(sims))            else:                max_sim = 0            answer_container = st.empty()            full_answer = ""            # 判断是否命中            if max_sim < SIM_THRESHOLD:            # 文字回答流式渲染              ans_generator = generate_answer(question)              for token in ans_generator:                full_answer += token                answer_container.markdown(f"**模型文字回答(无相关图片):** {full_answer}")            else:            # 显示图片              best = st.session_state.img_paths[idx]              img = PIL.Image.open(best)              st.image(img, caption=f"最相关图片(相关度:{max_sim:.2f})", use_column_width=True)            # 图文回答流式渲染              ans_generator = generate_answer(question, img)              for token in ans_generator:                full_answer += token                answer_container.markdown(f"**模型回答:** {full_answer}")elif page == "图库":    st.header("  已录入图片图库")    paths = st.session_state.img_paths    if not paths:        st.info("""啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊!宝宝肚肚打雷啦 。""")        st.info("""一张图片都没有**❗️请先在“上传”页面添加图片❗️**""")        st.info("""啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊!""")    else:        for i in range(0, len(paths), 3):            cols = st.columns(3)            for j, path in enumerate(paths[i:i+3]):                with cols[j]:                    img = PIL.Image.open(path)                    st.image(img, width=150, caption=os.path.basename(path))


03

注意事项

(1)使用前-上传图片

想要检索图片,你先得上传图片,我这里支持 3 种上传方式:

  • 上传单张图片
  • 输入图片的 url 进行自动下载
  • 把一大堆图片放进 zip 压缩包里,上传后台自动处理

图片

(2)各种库的版本

要是出现问题,大概率是库版本对不上。我曾经在一个项目里把 numpy 升级到了 2.0+ 版本,整个项目瘫痪,赶紧换回以前的版本(悲)。

Python 版本: 3.11.12requests 版本: 2.32.3numpy 版本: 2.0.2PIL/Pillow 版本: 11.2.1streamlit 版本: 1.45.0openai 版本: 1.76.0cohere 版本: 5.15.0

(3)非本地运行方法(colab)

如果你想在 colab 上直接运行,可以参考我以前的文章:

https://zhuanlan.zhihu.com/p/30771092833

(4)正确使用

这是一个图像检索 RAG,不是猫娘 RAG,不要局限于猫娘啦。

(5)模型选用

如果你不想用文中的模型,都可以换,注意把 base_url 和 api key 给换了,此外,还要查看对应的 API 调用示例。

04、可优化的点

1.我想到一个功能,让用户输入一个关键词,后台自动去下载对应的图片,bing-image-downloader 这个库估计挺合适。简单来说,爬虫。

2.如果本项目不用于猫娘检索,可以用 gemini 代替 Qwen3-235b-a22b 和 qvq-max-2025-03-25,因为 gemini 很容易把动漫角色给 block 掉。

最后的最后

感谢你们的阅读和喜欢,作为一位在一线互联网行业奋斗多年的老兵,我深知在这个瞬息万变的技术领域中,持续学习和进步的重要性。

为了帮助更多热爱技术、渴望成长的朋友,我特别整理了一份涵盖大模型领域的宝贵资料集。

这些资料不仅是我多年积累的心血结晶,也是我在行业一线实战经验的总结。

这些学习资料不仅深入浅出,而且非常实用,让大家系统而高效地掌握AI大模型的各个知识点。如果你愿意花时间沉下心来学习,相信它们一定能为你提供实质性的帮助。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

大模型知识脑图

为了成为更好的 AI大模型 开发者,这里为大家提供了总的路线图。它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
在这里插入图片描述

经典书籍阅读

阅读AI大模型经典书籍可以帮助读者提高技术水平,开拓视野,掌握核心技术,提高解决问题的能力,同时也可以借鉴他人的经验。对于想要深入学习AI大模型开发的读者来说,阅读经典书籍是非常有必要的。

在这里插入图片描述

实战案例

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

在这里插入图片描述

面试资料

我们学习AI大模型必然是想找到高薪的工作,下面这些面试题都是总结当前最新、最热、最高频的面试题,并且每道题都有详细的答案,面试前刷完这套面试题资料,小小offer,不在话下

在这里插入图片描述

640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

在这里插入图片描述

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值