大数跨境
0
0

全模态【Qwen-Omni】:图文输入、语音输出,重新定义AI助手

全模态【Qwen-Omni】:图文输入、语音输出,重新定义AI助手 我爱数据科学
2025-11-23
4
导读:【Qwen-Omni】:全模态大模型快速上手Qwen-Omni 模型支持传入多张图片。

【Qwen-Omni】:全模态大模型快速上手

Qwen-Omni 模型支持传入多张图片。对输入图片的要求如下:

  • 单个图片文件的大小不超过10 MB;
  • 图片数量受模型图文总 Token 上限(即最大输入)的限制,所有图片的总 Token 数必须小于模型的最大输入;
  • 图片的宽度和高度均应大于10像素,宽高比不应超过200:1或1:200;
  • 支持的图片类型请参见视觉理解。

以下示例代码以传入图片公网 URL 为例,当前只支持以流式输出的方式进行调用。

# 初始化OpenAI客户端,配置API连接参数
client = OpenAI(
    api_key = dash_api_key,      # 使用传入的API密钥进行身份验证
    base_url = dash_base_url,    # 设置API服务的基础URL地址
)

# 创建多模态聊天补全请求
completion = client.chat.completions.create(
    model="qwen3-omni-flash", # 指定使用的模型为Qwen3-Omni-Flash
    # 构建消息内容,包含多模态输入
    messages=[
        {
            "role": "user",      # 消息角色为用户
            "content": [         # 消息内容包含多个部分
                {
                    "type": "image_url",     # 第一部分:图片URL
                    "image_url": {
                        "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"  # 图片网络地址
                    },
                },
                {"type": "text", "text": "图中描绘的是什么景象?"},  # 第二部分:文本问题
            ],
        },
    ],
    # 设置输出数据的模态,指定需要返回的内容类型
    modalities=["text", "audio"],  # 要求同时返回文本和音频两种输出
    # 音频输出配置
    audio={"voice": "Cherry", "format": "wav"},  # 使用Cherry音色,输出WAV格式音频
    # 流式传输设置
    stream=True,  # 启用流式传输,实时接收响应数据
    # 流式传输选项配置
    stream_options={
        "include_usage": True  # 在流式响应中包含用量统计信息
    }
)

# 处理流式响应数据
for chunk in completion:
    # 检查当前数据块是否包含AI生成的内容
    if chunk.choices:
        # 打印AI返回的增量内容(文本或音频数据)
        print(chunk.choices[0].delta)
    else:
        # 如果没有生成内容,则打印API用量统计信息
        print(chunk.usage)

以下代码实时打印模型对图片的文字描述,并将将语音回答保存为本地WAV文件。

# 运行前的准备工作:
# 运行下列命令安装第三方依赖
# pip install numpy soundfile openai

import os
import base64
import soundfile as sf
import numpy as np
from openai import OpenAI

# 初始化OpenAI客户端,配置API连接参数
client = OpenAI(
    api_key=dash_api_key,      # 使用传入的API密钥进行身份验证
    base_url=dash_base_url,    # 设置API服务的基础URL地址
)

try:
    # 创建多模态聊天补全请求
    completion = client.chat.completions.create(
        model="qwen3-omni-flash", # 指定使用的模型为Qwen3-Omni-Flash
        # 构建消息内容,包含多模态输入
        messages=[
            {
                "role": "user",      # 消息角色为用户
                "content": [         # 消息内容包含多个部分
                    {
                        "type": "image_url",     # 第一部分:图片URL
                        "image_url": {
                            "url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"  # 图片网络地址
                        },
                    },
                    {"type": "text", "text": "图中描绘的是什么景象?"},  # 第二部分:文本问题
                ],
            },
        ],
        # 设置输出数据的模态
        modalities=["text", "audio"],  # 要求同时返回文本和音频两种输出
        # 音频输出配置
        audio={"voice": "Cherry", "format": "wav"},  # 使用Cherry音色,输出WAV格式音频
        # 流式传输设置
        stream=True,  # 启用流式传输,实时接收响应数据
        # 流式传输选项配置
        stream_options={
            "include_usage": True  # 在流式响应中包含用量统计信息
        }
    )

    # 处理流式响应并解码音频
    print("模型对图片的描述:")
    audio_base64_string = ""  # 用于累积音频的base64数据

    for chunk in completion:
        # 处理文本部分:打印模型生成的文本内容
        if chunk.choices and chunk.choices[0].delta.content:
            print(chunk.choices[0].delta.content, end="", flush=True)

        # 收集音频部分:累积音频的base64数据
        if (chunk.choices and 
            hasattr(chunk.choices[0].delta, "audio") and 
            chunk.choices[0].delta.audio and
            chunk.choices[0].delta.audio.get("data")):
            audio_base64_string += chunk.choices[0].delta.audio["data"]

    # 保存音频文件到本地
    if audio_base64_string:
        # 解码base64音频数据
        wav_bytes = base64.b64decode(audio_base64_string)
        # 将字节数据转换为numpy数组
        audio_np = np.frombuffer(wav_bytes, dtype=np.int16)
        # 保存为WAV文件,采样率为24000Hz
        sf.write("image_description.wav", audio_np, samplerate=24000)
        print(f"\n音频文件已保存至:image_description.wav")
    else:
        print("\n未接收到音频数据")

except Exception as e:
    print(f"请求失败: {e}")

输出为:

模型对图片的描述:
图中是一位女士和一只狗在海滩上互动的温馨场景。夕阳西下,海浪轻拍沙滩,女士坐在沙地上,与穿着彩色背带的金毛犬击掌,气氛轻松愉快。
音频文件已保存至:image_description.wav

自定义函数,支持多种模式

  • 文本+音频(流式)
  • 仅文本(流式)
  • 仅文本(非流式)
  • 生成音频但不保存文件
import base64
import soundfile as sf
import numpy as np

def analyze_image(client, image_url, question="图中描绘的是什么景象?", 
                 voice="Cherry", audio_format="wav", 
                 generate_audio=True, save_audio=True, 
                 audio_filename="image_description.wav", 
                 print_text=True, samplerate=24000,
                 stream=True):
    """
    分析图片并获取文本描述,可选择是否生成语音回复

    参数:
        client: OpenAI客户端实例
        image_url (str): 图片的URL地址
        question (str): 对图片的提问,默认为"图中描绘的是什么景象?"
        voice (str): 语音类型,默认为"Cherry"(仅在generate_audio=True时有效)
        audio_format (str): 音频格式,默认为"wav"(仅在generate_audio=True时有效)
        generate_audio (bool): 是否生成音频,默认为True
        save_audio (bool): 是否保存音频文件,默认为True(仅在generate_audio=True时有效)
        audio_filename (str): 音频文件名,默认为"image_description.wav"(仅在generate_audio=True且save_audio=True时有效)
        print_text (bool): 是否打印文本回复,默认为True
        samplerate (int): 音频采样率,默认为24000(仅在generate_audio=True时有效)
        stream (bool): 是否使用流式传输,默认为True

    返回:
        dict: 包含文本回复和音频数据的字典
    """

    try:
        # 构建多模态消息内容
        messages = [
            {
                "role": "user",
                "content": [
                    {
                        "type": "image_url",
                        "image_url": {"url": image_url},
                    },
                    {"type": "text", "text": question},
                ],
            }
        ]

        # 准备请求参数
        request_params = {
            "model": "qwen3-omni-flash",
            "messages": messages,
            "stream": stream,
        }

        # 根据是否生成音频设置参数
        if generate_audio:
            request_params["modalities"] = ["text", "audio"]
            request_params["audio"] = {"voice": voice, "format": audio_format}
            request_params["stream_options"] = {"include_usage": True}
        else:
            request_params["modalities"] = ["text"]
            # 如果不生成音频,强制关闭流式传输以获得完整响应
            if not stream:
                request_params["stream"] = False

        # 发起聊天补全请求
        completion = client.chat.completions.create(**request_params)

        # 初始化变量
        text_response = ""  # 存储完整的文本回复
        audio_base64_string = ""  # 存储音频的base64数据

        if print_text:
            print("模型回复:")

        # 处理响应(流式或非流式)
        if stream:
            # 流式处理
            for chunk in completion:
                # 处理文本部分
                if chunk.choices and chunk.choices[0].delta.content:
                    chunk_text = chunk.choices[0].delta.content
                    text_response += chunk_text
                    if print_text:
                        print(chunk_text, end="", flush=True)

                # 收集音频部分(仅在生成音频时)
                if (generate_audio and chunk.choices and 
                    hasattr(chunk.choices[0].delta, "audio") and 
                    chunk.choices[0].delta.audio and
                    chunk.choices[0].delta.audio.get("data")):
                    audio_base64_string += chunk.choices[0].delta.audio["data"]
        else:
            # 非流式处理
            text_response = completion.choices[0].message.content
            if print_text:
                print(text_response)

        # 保存音频文件(仅在生成音频且需要保存时)
        audio_data = None
        if generate_audio and audio_base64_string and save_audio:
            wav_bytes = base64.b64decode(audio_base64_string)
            audio_data = np.frombuffer(wav_bytes, dtype=np.int16)
            sf.write(audio_filename, audio_data, samplerate=samplerate)
            if print_text:
                print(f"\n音频文件已保存至:{audio_filename}")

        # 返回结果
        result = {
            "success": True,
            "text": text_response,
            "audio_base64": audio_base64_string if generate_audio else "",
            "audio_data": audio_data if generate_audio else None,
            "audio_saved": (generate_audio and save_audio and bool(audio_base64_string)),
            "audio_filename": audio_filename if (generate_audio and save_audio and audio_base64_string) else None,
            "generate_audio": generate_audio,
            "image_url": image_url,
            "question": question
        }

        return result

    except Exception as e:
        print(f"图片分析请求失败: {e}")
        return {
            "success": False,
            "text": "",
            "audio_base64": "",
            "audio_data": None,
            "audio_saved": False,
            "audio_filename": None,
            "generate_audio": generate_audio,
            "error": str(e),
            "image_url": image_url,
            "question": question
        }

示例1:完整功能(文本+音频)- 流式

# 示例1:完整功能(文本+音频)- 流式
print("=== 示例1:完整功能(文本+音频)===")
result1 = analyze_image(
    client=client,
    image_url="https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg",
    question="图中描绘的是什么景象?请详细描述。",
    generate_audio=True,
    save_audio=True,
    audio_filename="full_analysis.wav",
    print_text=True,
    stream=True
)

输出为:

=== 示例1:完整功能(文本+音频)===
模型回复:
图中描绘的是一幅温馨的海滩场景。夕阳西下,金色的阳光洒在沙滩上,海浪轻轻拍打着岸边。一位年轻女子坐在沙滩上,穿着格子衬衫和牛仔裤,面带微笑,与她身旁的一只金毛犬互动。这只狗戴着彩色的项圈,正用前爪轻轻搭在女子的手上,仿佛在进行“击掌”游戏。整个画面充满了宁静与和谐,展现了人与宠物之间深厚的情感纽带。
音频文件已保存至:full_analysis.wav

示例2:仅文本分析 - 流式

# 示例2:仅文本分析 - 流式
print("\n=== 示例2:仅文本分析(流式)===")
result2 = analyze_image(
    client=client,
    image_url="https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg",
    question="图片中有哪些主要元素?",
    generate_audio=False,
    print_text=True,
    stream=True
)

输出为:

=== 示例2:仅文本分析(流式)===
模型回复:
好的,根据您提供的图片,以下是其中的主要元素:

- **人物**:一位年轻女性,她坐在沙滩上,面带微笑地看着她的狗。她穿着一件黑白格子衬衫和深色裤子。
- **动物**:一只金毛寻回犬,它戴着一个带有彩色图案的胸背带,正坐着并抬起一只前爪与女子互动。
- **互动**:女子和狗正在进行一个类似“击掌”的互动,女子伸出一只手,狗用爪子轻轻触碰,展现了它们之间亲密和愉快的关系。
- **环境**:场景是在一个海滩上。背景是平静的海面和天空,可以看到海浪在岸边拍打。

示例3:仅文本分析 - 非流式

# 示例3:仅文本分析 - 非流式
print("\n=== 示例3:仅文本分析(非流式)===")
result3 = analyze_image(
    client=client,
    image_url="https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg",
    question="用一句话描述图片内容",
    generate_audio=False,
    print_text=True,
    stream=False
)

输出为:

=== 示例3:仅文本分析(非流式)===
模型回复:
在夕阳下的海滩上,一位年轻女子与一只戴着彩色背带的金毛犬开心地击掌互动。

示例4:生成音频但不保存文件

# 示例4:生成音频但不保存文件
print("\n=== 示例4:生成音频但不保存文件 ===")
result4 = analyze_image(
    client=client,
    image_url="https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg",
    question="简单描述图片",
    generate_audio=True,
    save_audio=False,  # 只生成但不保存
    print_text=True,
    stream=True
)

输出为:

=== 示例4:生成音频但不保存文件 ===
模型回复:
这是一张在海滩上拍摄的照片。画面中,一位女士坐在沙滩上,她穿着格子衬衫和深色裤子,面带微笑,正在和一只狗互动。这只狗是一只金毛犬,戴着彩色的项圈,正用前爪轻轻搭在女士的手上,似乎在“握手”。背景是平静的海面和柔和的夕阳,阳光洒在沙滩上,营造出温暖、宁静的氛围。整个场景显得非常温馨、和谐,展现了人与宠物之间的亲密关系。

打印结果摘要

# 打印结果摘要
print("\n=== 结果摘要 ===")
for i, result in enumerate([result1, result2, result3, result4], 1):
    print(f"示例{i}: 成功={result['success']}, 文本长度={len(result['text'])}, "
          f"生成音频={result['generate_audio']}, 音频保存={result['audio_saved']}")

输出为:

=== 结果摘要 ===
示例1: 成功=True, 文本长度=150, 生成音频=True, 音频保存=True
示例2: 成功=True, 文本长度=288, 生成音频=False, 音频保存=False
示例3: 成功=True, 文本长度=36, 生成音频=False, 音频保存=False
示例4: 成功=True, 文本长度=159, 生成音频=True, 音频保存=False

传入本地图片:输入 Base64 编码的本地文件。我们修改上面的函数,用于支持读取本地图片进行分析。

def analyze_local_image(client, image_path, question="图中描绘的是什么景象?", 
                       voice="Cherry", audio_format="wav", 
                       generate_audio=True, save_audio=True, 
                       audio_filename="image_description.wav", 
                       print_text=True, samplerate=24000,
                       stream=True):
    """
    分析本地图片并获取文本描述,可选择是否生成语音回复

    参数:
        client: OpenAI客户端实例
        image_path (str): 本地图片文件路径
        question (str): 对图片的提问,默认为"图中描绘的是什么景象?"
        voice (str): 语音类型,默认为"Cherry"(仅在generate_audio=True时有效)
        audio_format (str): 音频格式,默认为"wav"(仅在generate_audio=True时有效)
        generate_audio (bool): 是否生成音频,默认为True
        save_audio (bool): 是否保存音频文件,默认为True(仅在generate_audio=True时有效)
        audio_filename (str): 音频文件名,默认为"image_description.wav"(仅在generate_audio=True且save_audio=True时有效)
        print_text (bool): 是否打印文本回复,默认为True
        samplerate (int): 音频采样率,默认为24000(仅在generate_audio=True时有效)
        stream (bool): 是否使用流式传输,默认为True

    返回:
        dict: 包含文本回复和音频数据的字典
    """

    try:
        # 检查图片文件是否存在
        if not os.path.exists(image_path):
            raise FileNotFoundError(f"图片文件不存在: {image_path}")

        # 检查文件格式
        valid_extensions = {'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'}
        file_ext = os.path.splitext(image_path)[1].lower()
        if file_ext not in valid_extensions:
            raise ValueError(f"不支持的图片格式: {file_ext},支持的格式: {valid_extensions}")

        # 读取图片文件并编码为base64
        with open(image_path, "rb") as image_file:
            image_data = image_file.read()
            base64_image = base64.b64encode(image_data).decode('utf-8')

        # 构建数据URL
        mime_type = "image/png" if file_ext == '.png' else \
                   "image/jpeg" if file_ext in ['.jpg', '.jpeg'] else \
                   "image/gif" if file_ext == '.gif' else \
                   "image/bmp" if file_ext == '.bmp' else \
                   "image/webp"

        image_url = f"data:{mime_type};base64,{base64_image}"

        # 构建多模态消息内容
        messages = [
            {
                "role": "user",
                "content": [
                    {
                        "type": "image_url",
                        "image_url": {"url": image_url},
                    },
                    {"type": "text", "text": question},
                ],
            }
        ]

        # 准备请求参数
        request_params = {
            "model": "qwen3-omni-flash",
            "messages": messages,
            "stream": stream,
        }

        # 根据是否生成音频设置参数
        if generate_audio:
            request_params["modalities"] = ["text", "audio"]
            request_params["audio"] = {"voice": voice, "format": audio_format}
            request_params["stream_options"] = {"include_usage": True}
        else:
            request_params["modalities"] = ["text"]
            # 如果不生成音频,强制关闭流式传输以获得完整响应
            if not stream:
                request_params["stream"] = False

        # 发起聊天补全请求
        completion = client.chat.completions.create(**request_params)

        # 初始化变量
        text_response = ""  # 存储完整的文本回复
        audio_base64_string = ""  # 存储音频的base64数据

        if print_text:
            print("模型回复:")

        # 处理响应(流式或非流式)
        if stream:
            # 流式处理
            for chunk in completion:
                # 处理文本部分
                if chunk.choices and chunk.choices[0].delta.content:
                    chunk_text = chunk.choices[0].delta.content
                    text_response += chunk_text
                    if print_text:
                        print(chunk_text, end="", flush=True)

                # 收集音频部分(仅在生成音频时)
                if (generate_audio and chunk.choices and 
                    hasattr(chunk.choices[0].delta, "audio") and 
                    chunk.choices[0].delta.audio and
                    chunk.choices[0].delta.audio.get("data")):
                    audio_base64_string += chunk.choices[0].delta.audio["data"]
        else:
            # 非流式处理
            text_response = completion.choices[0].message.content
            if print_text:
                print(text_response)

        # 保存音频文件(仅在生成音频且需要保存时)
        audio_data = None
        if generate_audio and audio_base64_string and save_audio:
            wav_bytes = base64.b64decode(audio_base64_string)
            audio_data = np.frombuffer(wav_bytes, dtype=np.int16)
            sf.write(audio_filename, audio_data, samplerate=samplerate)
            if print_text:
                print(f"\n音频文件已保存至:{audio_filename}")

        # 返回结果
        result = {
            "success": True,
            "text": text_response,
            "audio_base64": audio_base64_string if generate_audio else "",
            "audio_data": audio_data if generate_audio else None,
            "audio_saved": (generate_audio and save_audio and bool(audio_base64_string)),
            "audio_filename": audio_filename if (generate_audio and save_audio and audio_base64_string) else None,
            "generate_audio": generate_audio,
            "image_path": image_path,
            "image_size": len(image_data),
            "question": question
        }

        return result

    except Exception as e:
        print(f"图片分析请求失败: {e}")
        return {
            "success": False,
            "text": "",
            "audio_base64": "",
            "audio_data": None,
            "audio_saved": False,
            "audio_filename": None,
            "generate_audio": generate_audio,
            "error": str(e),
            "image_path": image_path,
            "question": question
        }

本地有一张engle.png的图片。

简单调用

result = analyze_local_image(
    client=client,
    image_path="eagle.png",
    question="描述这张图片"
)

输出为:

模型回复:
这是一只白头海雕在蓝天白云间展翅飞翔。它有着标志性的白色头部和尾部,深棕色的身体和翅膀,黄色的喙和爪子。翅膀完全展开,姿态雄健有力,背景是层次分明的云朵,整体画面充满力量与自由感。
音频文件已保存至:local_image_description.wav

您的浏览器不支持音频元素。

高级调用

result = analyze_local_image(
    client=client,
    image_path="eagle.png",
    question="这只鹰有什么特征?",
    generate_audio=True,
    audio_filename="eagle_analysis.wav",
    voice="Cherry"
)

输出为:

模型回复:
这是一只白头海雕,特征很明显:头部和尾部是白色的,身体和翅膀是深棕色的。它有黄色的喙和爪子,看起来非常威武。
音频文件已保存至:eagle_analysis.wav

【Qwen3-VL】:多模态AI新玩法! 基于Qwen3-VL的文档智能解析(一)

【Qwen3-VL】:多模态AI新玩法! 基于Qwen3-VL的文档智能解析(二)将图片解析为Markdown

【Qwen3-VL】:多模态AI新玩法! 基于Qwen3-VL的文档智能解析(三)对图片解析Markdown结果进行智能问答

【Qwen3-VL】:多模态AI新玩法! 基于Qwen3-VL的文档智能解析(四)搭建文档解析平台

【Qwen3-VL】:多模态AI新玩法! 基于Qwen3-VL的文档智能解析(五)搭建功能完善的文档解析平台

【Qwen3-VL】:多模态AI新玩法! 基于Streamlit搭建长文档智能解析平台


【声明】内容源于网络
0
0
我爱数据科学
精通R语言及Python,传递数据挖掘及可视化技术,关注机器学习及深度学习算法及实现,分享大模型及LangChain的使用技巧。编著多本R语言、python、深度学习等书籍。
内容 322
粉丝 0
我爱数据科学 精通R语言及Python,传递数据挖掘及可视化技术,关注机器学习及深度学习算法及实现,分享大模型及LangChain的使用技巧。编著多本R语言、python、深度学习等书籍。
总阅读128
粉丝0
内容322