第14章:多模态AI实战 —— 让AI"看懂"图片和文档

本章目标:掌握多模态 AI 的核心概念,使用阿里云百炼 Qwen-VL 模型实现图像理解、视觉问答、结构化信息提取等实际场景,能够将多模态能力集成到生产 AI 应用中。


前期回顾

AI入门开发系列文章合集


本章你将学到

  • 多模态 AI 与纯文本 AI 的本质区别和应用场景
  • Qwen-VL 视觉语言模型的能力边界与调用方式
  • 用 OpenAI SDK 和 LangChain 两种方式调用多模态 API
  • 从图像中提取结构化 JSON 数据(零代码图像解析)
  • 链式思维(CoT)视觉推理:让模型"分步思考"图像
  • 实际生产场景:电商图文描述、文档数字化、质量检测

一、什么是多模态AI?

1.1 文本AI vs 多模态AI

以往,AI 只能理解文字。你输入"描述一只猫",它输出文字描述。但它"看不见"图片,无法告诉你一张照片里的猫是什么颜色、多大、在做什么。

多模态 AI 打破了这一限制------它能同时理解文字、图片(部分模型还支持视频、音频)。

markdown 复制代码
文本 AI:
  输入:文字 → 输出:文字

多模态 AI:
  输入:图片 + 文字 → 输出:文字(或结构化数据)
       图片 + 图片 + 文字 → 输出:对比分析

1.2 如果不使用多模态AI,会有什么问题?

场景 没有多模态AI 使用多模态AI
电商商品上架 人工写商品描述,耗时 5-10 分钟/商品 上传图片,1 秒自动生成描述
用户发图咨询客服 人工查看图片回复,响应慢 AI 自动理解图片并回复
扫描文档数字化 人工录入,出错率高 AI 自动识别提取结构化信息
产品质量检测 人工对比,主观差异大 AI 客观比较标准图与实物

1.3 多模态AI能做什么?


二、核心模型:Qwen-VL 系列

2.1 模型选择

阿里云百炼提供 Qwen-VL 系列视觉语言模型,通过 OpenAI 兼容端点调用:

模型 特点 适用场景
qwen-vl-plus 均衡性能,速度快,成本低 大多数视觉任务(本章示例)
qwen-vl-max 最强视觉理解,支持复杂推理 高精度场景、复杂图表解析

💡 建议 :开发和测试阶段用 qwen-vl-plus,生产上线前评估是否需要升级到 qwen-vl-max

2.2 多模态消息格式

与纯文本 API 的关键区别:content 字段不再是字符串,而是一个混合节点列表

python 复制代码
# 纯文本(第1章的格式)
messages = [
    {"role": "user", "content": "你好"}
]

# 多模态(第14章的格式)
messages = [
    {
        "role": "user",
        "content": [
            # 节点1:图像
            {"type": "image_url", "image_url": {"url": "https://example.com/photo.jpg"}},
            # 节点2:文字指令
            {"type": "text", "text": "描述这张图片"}
        ]
    }
]

关键点

  • content 变成列表,可以混合多个节点
  • 图像节点:{"type": "image_url", "image_url": {"url": "..."}}
  • 文字节点:{"type": "text", "text": "..."}
  • 一次调用可以包含多张图片(在列表中放多个图像节点)

三、快速上手:基础图像理解

3.1 初始化客户端

python 复制代码
# lessons/14_multimodal/01_vision_basics.py

import os
import sys
from openai import OpenAI

def create_client() -> OpenAI:
    """创建百炼 API 客户端,多模态与纯文本使用相同的客户端。"""
    api_key = os.getenv("DASHSCOPE_API_KEY")
    if not api_key:
        print("错误:请设置环境变量 DASHSCOPE_API_KEY")
        sys.exit(1)
    return OpenAI(
        api_key=api_key,
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    )

📌 多模态模型与文本模型使用同一个客户端 ,只是 model 参数和 content 格式不同。

3.2 Demo 1:基础图像描述

python 复制代码
# 公共测试图像(DashScope 官方示例图)
IMAGE_DOG_AND_GIRL = "https://dashscope.oss-cn-beijing.aliyuncs.com/images/dog_and_girl.jpeg"

def demo_image_description(client: OpenAI) -> str:
    """让模型用自然语言描述一张图片。"""
    print("\n🖼️  Demo 1:基础图像描述")
    print(f"   图像 URL:{IMAGE_DOG_AND_GIRL}")

    response = client.chat.completions.create(
        model="qwen-vl-plus",
        messages=[
            {
                "role": "user",
                "content": [
                    {"type": "image_url", "image_url": {"url": IMAGE_DOG_AND_GIRL}},
                    {"type": "text", "text": "请详细描述这张图片的内容,包括人物、动物、环境和氛围。"}
                ],
            }
        ],
    )
    description = response.choices[0].message.content
    print(f"   📝 描述结果:\n   {description}")
    return description

运行效果:

arduino 复制代码
🖼️  Demo 1:基础图像描述
   图像 URL:https://dashscope.oss-cn-beijing.aliyuncs.com/images/dog_and_girl.jpeg
   📝 描述结果:
   图片中是一位女孩和一只金毛猎犬在户外草地上互动...

3.3 Demo 2:视觉问答

python 复制代码
IMAGE_ANT = "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"

def demo_visual_qa(client: OpenAI) -> str:
    """针对图像提出具体问题,获取精准答案。"""
    print("\n❓ Demo 2:视觉问答")

    response = client.chat.completions.create(
        model="qwen-vl-plus",
        messages=[
            {
                "role": "user",
                "content": [
                    {"type": "image_url", "image_url": {"url": IMAGE_ANT}},
                    {"type": "text", "text": (
                        "请回答以下问题:\n"
                        "1. 这是什么动物?\n"
                        "2. 图中有几只?\n"
                        "3. 它们在做什么?\n"
                        "4. 图像风格是什么(特写/全景/微距等)?"
                    )},
                ],
            }
        ],
    )
    answer = response.choices[0].message.content
    print(f"   💡 回答:\n   {answer}")
    return answer

为什么视觉问答有价值:

  • 用户上传产品图片,AI 自动回答"这个零件的型号是多少"
  • 用户拍照上传病历单,AI 提取关键检查指标
  • 自动审核用户上传内容是否符合平台规范

3.4 Demo 3:多图对比

同一次调用传入两张图片,让模型对比分析:

python 复制代码
def demo_multi_image_comparison(client: OpenAI) -> str:
    """在一次 API 调用中传入两张图片,进行对比分析。"""
    print("\n🔍 Demo 3:多图对比分析")

    response = client.chat.completions.create(
        model="qwen-vl-plus",
        messages=[
            {
                "role": "user",
                "content": [
                    # 图像1
                    {"type": "image_url", "image_url": {"url": IMAGE_DOG_AND_GIRL}},
                    # 图像2  
                    {"type": "image_url", "image_url": {"url": IMAGE_ANT}},
                    # 问题
                    {"type": "text", "text": "请对比这两张图片,从主体内容、拍摄风格、色调三个维度进行分析。"},
                ],
            }
        ],
    )
    comparison = response.choices[0].message.content
    print(f"   📊 对比结果:\n   {comparison}")
    return comparison

⚠️ 注意 :两张图片放在同一个 content 列表中,API 会自动关联它们的上下文。


四、进阶:结构化信息提取

4.1 为什么需要结构化输出?

视觉理解给你自然语言描述,但业务系统需要结构化数据

json 复制代码
纯描述(不可用于系统):
  "这是一个红色的苹果手机,8GB内存,价格大约在5000元左右"

结构化输出(可直接写入数据库):
  {
    "brand": "Apple",
    "color": "红色",
    "memory": "8GB",
    "estimated_price": 5000
  }

4.2 Demo:JSON 结构化提取

python 复制代码
# lessons/14_multimodal/02_structured_extraction.py

import json
import re

def demo_json_extraction(client: OpenAI) -> dict:
    """从图像中提取结构化 JSON 数据。"""
    print("\n📋 Demo:JSON 结构化提取")

    prompt = """请分析这张图片,以 JSON 格式返回以下信息:
{
  "main_subject": "图片主体(一句话)",
  "scene_type": "场景类型(室内/室外/自然/城市等)",
  "lighting": "光线条件(明亮/昏暗/自然光/人工光等)",
  "dominant_colors": ["主要颜色列表"],
  "objects_count": "可见主要物体数量(数字)",
  "image_quality": "图像质量(高/中/低)",
  "mood": "整体氛围(一个词)"
}
只返回 JSON,不要其他文字。"""

    response = client.chat.completions.create(
        model="qwen-vl-plus",
        messages=[
            {
                "role": "user",
                "content": [
                    {"type": "image_url", "image_url": {"url": IMAGE_DOG_AND_GIRL}},
                    {"type": "text", "text": prompt},
                ],
            }
        ],
    )

    raw = response.choices[0].message.content
    # 提取 JSON 块(处理模型可能带 markdown 代码块的情况)
    json_match = re.search(r"\{.*\}", raw, re.DOTALL)
    if json_match:
        data = json.loads(json_match.group())
        print(f"   ✅ 提取成功:\n   {json.dumps(data, ensure_ascii=False, indent=4)}")
        return data

    return {}

关键技巧

  1. 在提示词中明确说明 JSON 结构和字段含义
  2. re.search(r"\{.*\}", raw, re.DOTALL) 提取 JSON,因为模型可能带 markdown 代码块
  3. 加上 json.loads() 的异常处理

4.3 链式思维视觉推理

对于复杂的视觉分析,让模型"分步思考"比一步到位更准确:

python 复制代码
def demo_chain_of_thought_reasoning(client: OpenAI) -> str:
    """使用链式思维(CoT)引导模型进行深度视觉推理。"""
    print("\n🧠 Demo:链式思维视觉推理")

    cot_prompt = """请按照以下步骤分析这张图片:

步骤1 - 观察:描述你看到的所有视觉元素(物体、人物、背景)
步骤2 - 分析:分析这些元素之间的关系和图片的核心主题
步骤3 - 推断:基于以上分析,推断这张图片可能的用途或背景故事

请按"步骤1:"、"步骤2:"、"步骤3:"的格式逐步输出。"""

    response = client.chat.completions.create(
        model="qwen-vl-plus",
        messages=[
            {
                "role": "user",
                "content": [
                    {"type": "image_url", "image_url": {"url": IMAGE_DOG_AND_GIRL}},
                    {"type": "text", "text": cot_prompt},
                ],
            }
        ],
    )
    reasoning = response.choices[0].message.content
    print(f"   🔍 推理过程:\n{reasoning}")
    return reasoning

4.4 LangChain 集成方式

python 复制代码
# LangChain 多模态调用方式
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI

def demo_langchain_multimodal() -> str:
    """使用 LangChain ChatOpenAI 进行多模态调用。"""
    llm = ChatOpenAI(
        model="qwen-vl-plus",
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
        api_key=os.getenv("DASHSCOPE_API_KEY"),
    )

    # HumanMessage 的 content 同样是列表格式
    message = HumanMessage(
        content=[
            {"type": "image_url", "image_url": {"url": IMAGE_DOG_AND_GIRL}},
            {"type": "text", "text": "这张图片里有什么?请简洁描述。"},
        ]
    )

    response = llm.invoke([message])
    return response.content

五、OpenAI SDK vs LangChain 调用方式对比

维度 OpenAI SDK(直接调用) LangChain 封装
代码量 少,更直观 稍多,但更结构化
与LangChain集成 需要手动桥接 原生集成,可直接接 Chain
多模态消息格式 client.chat.completions.create() ChatOpenAI.invoke([HumanMessage(...)])
结构化输出 手动 JSON 解析 可用 with_structured_output(PydanticModel)
流式输出 stream=True .stream() 方法
推荐场景 简单视觉任务、快速原型 复杂应用、需要与 Chain/Agent 集成
python 复制代码
# LangChain with_structured_output 示例
from pydantic import BaseModel, Field

class ImageMetadata(BaseModel):
    main_subject: str = Field(description="图片主体描述")
    scene_type: str = Field(description="场景类型")
    dominant_colors: list[str] = Field(description="主要颜色列表")
    mood: str = Field(description="整体氛围")

structured_llm = llm.with_structured_output(ImageMetadata)
result = structured_llm.invoke([HumanMessage(content=[
    {"type": "image_url", "image_url": {"url": IMAGE_DOG_AND_GIRL}},
    {"type": "text", "text": "分析这张图片"},
])])
# result 是 ImageMetadata 对象,result.main_subject 直接访问

六、实战场景演示

6.1 电商场景:自动生成商品描述

python 复制代码
def generate_product_description(client: OpenAI, product_image_url: str) -> dict:
    """
    给定商品图片 URL,自动生成电商商品描述。
    实际使用时,替换 product_image_url 为真实商品图片。
    """
    prompt = """你是一位专业的电商文案撰写员。
请分析这张商品图片,生成以下 JSON 格式的商品信息:
{
  "title": "商品标题(15字以内,突出核心卖点)",
  "category": "商品分类",
  "key_features": ["核心特点1", "核心特点2", "核心特点3"],
  "target_audience": "目标用户群体",
  "selling_points": "营销文案(50字以内,吸引点击)",
  "tags": ["标签1", "标签2", "标签3"]
}
只返回 JSON,不要其他内容。"""

    response = client.chat.completions.create(
        model="qwen-vl-plus",
        messages=[{
            "role": "user",
            "content": [
                {"type": "image_url", "image_url": {"url": product_image_url}},
                {"type": "text", "text": prompt},
            ]
        }]
    )
    raw = response.choices[0].message.content
    json_match = re.search(r"\{.*\}", raw, re.DOTALL)
    if json_match:
        return json.loads(json_match.group())
    return {"error": "解析失败", "raw": raw}

6.2 文档数字化:提取表单信息

python 复制代码
def extract_form_data(client: OpenAI, document_image_url: str) -> dict:
    """
    从文档/表单图片中提取结构化信息。
    适用于:发票、合同、报告、申请表等。
    """
    prompt = """请仔细阅读这张文档图片,提取所有可见的关键信息,
以 JSON 格式返回。根据文档类型自动判断提取哪些字段。
对于每个字段,如果图片中清晰可见,则提取;否则填 null。
只返回 JSON。"""

    response = client.chat.completions.create(
        model="qwen-vl-max",  # 文档理解用更强的模型
        messages=[{
            "role": "user",
            "content": [
                {"type": "image_url", "image_url": {"url": document_image_url}},
                {"type": "text", "text": prompt},
            ]
        }]
    )
    raw = response.choices[0].message.content
    json_match = re.search(r"\{.*\}", raw, re.DOTALL)
    if json_match:
        return json.loads(json_match.group())
    return {"raw_text": raw}

七、最佳实践与注意事项

7.1 图像质量要求

因素 建议
分辨率 最低 200×200 像素,推荐 800×600 以上
文件大小 不超过 20MB(百炼限制)
格式 JPEG、PNG、GIF(静态)、WEBP
清晰度 关键内容需清晰,模糊或过度压缩会影响识别
光线 避免过曝或过暗

7.2 本地图片:使用 Base64 编码

如果图片在本地(不是公网 URL),需要转换为 Base64:

python 复制代码
import base64

def image_to_base64(image_path: str) -> str:
    """将本地图片转换为 base64 data URL。"""
    with open(image_path, "rb") as f:
        image_data = base64.b64encode(f.read()).decode("utf-8")
    # 推断 MIME 类型
    suffix = image_path.lower().split(".")[-1]
    mime_map = {"jpg": "jpeg", "jpeg": "jpeg", "png": "png", "gif": "gif", "webp": "webp"}
    mime = mime_map.get(suffix, "jpeg")
    return f"data:image/{mime};base64,{image_data}"

# 使用方式:
data_url = image_to_base64("/path/to/local/image.jpg")
content = [
    {"type": "image_url", "image_url": {"url": data_url}},
    {"type": "text", "text": "描述这张图片"},
]

7.3 视觉提示词技巧

技巧 示例
明确任务类型 "提取图中所有文字" vs "描述图片"
指定输出格式 "以 JSON 格式返回" / "以列表形式"
限定关注范围 "只分析图中的人物,忽略背景"
链式思维 "先描述,再分析,最后给出结论"
角色设定 "你是一位专业摄影师,请评价这张照片的构图"

7.4 错误处理

python 复制代码
import httpx

def safe_vision_call(client: OpenAI, image_url: str, question: str) -> str:
    """带错误处理的视觉调用。"""
    try:
        response = client.chat.completions.create(
            model="qwen-vl-plus",
            messages=[{
                "role": "user",
                "content": [
                    {"type": "image_url", "image_url": {"url": image_url}},
                    {"type": "text", "text": question},
                ]
            }],
            timeout=30,  # 图像下载可能较慢,设置合理超时
        )
        return response.choices[0].message.content
    except Exception as e:
        # 常见错误:图像 URL 无法访问、格式不支持、内容违规
        print(f"⚠️ 视觉调用失败:{type(e).__name__}: {e}")
        return f"调用失败:{str(e)}"

7.5 成本与性能

  • 多模态调用比纯文本调用贵约 2-5 倍(图像 token 消耗较大)
  • 优先用 qwen-vl-plus,只有在精度不够时升级到 qwen-vl-max
  • 批量任务可用异步并发(asyncio.gather)提升吞吐量
  • 对于高频场景,考虑缓存已分析过的图像结果

八、快速运行本章代码

bash 复制代码
cd ai-agent-test

# 运行视觉基础演示
uv run python lessons/14_multimodal/01_vision_basics.py

# 运行结构化提取演示
uv run python lessons/14_multimodal/02_structured_extraction.py

本章总结

markdown 复制代码
学习路径回顾:
文字 AI(第1-5章)
    ↓ 加入图像输入
多模态 AI(第14章)
    ├── OpenAI SDK:直接、简洁
    └── LangChain:可组合、可扩展
            ↓
      视觉 Chain / 视觉 Agent(综合应用)
能力 已掌握
图像描述 demo_image_description()
视觉问答 demo_visual_qa()
多图对比 demo_multi_image_comparison()
结构化提取 demo_json_extraction()
CoT 推理 demo_chain_of_thought_reasoning()
LangChain 集成 HumanMessage + with_structured_output

下一步建议

  • 第15章 AI应用评估与测试 ------ 如何评估多模态 AI 的输出质量?
  • 尝试将视觉理解加入 RAG 管道(图文混合索引)
  • 探索更复杂场景:多轮视觉对话、视频关键帧分析

AI入门开发系列文章合集
作者:阿聪谈架构

公众号:阿聪谈架构 (分享后端架构 / AI / Java 技术文章)

相关代码关注公众号:【阿聪谈架构】 回复:AI专栏代码

相关推荐
心.c2 小时前
AI Agent 的新战场:从会动手,到被允许动手
人工智能·ai
救救孩子把2 小时前
89-机器学习与大模型开发数学教程-8-7 本书总结与展望
人工智能·机器学习
X54先生(人文科技)2 小时前
ELR-SELLM 碳硅光阴协同演进系统架构文档
人工智能·深度学习·系统架构·开源协议
Oneslide2 小时前
rsync 大数据量同步中断问题
后端
云烟成雨TD2 小时前
Spring AI 1.x 系列【39】MCP Java SDK 与 Spring AI 集成
java·人工智能·spring
继续商行2 小时前
性能优化的工程美学与极致追求
人工智能
超梦dasgg2 小时前
详细讲解 AI 上下文(Context)
人工智能·状态模式
救救孩子把2 小时前
87-机器学习与大模型开发数学教程-8-5 微分方程与神经微分方程(Neural ODEs)
人工智能·机器学习
完成大叔2 小时前
模块二,Agent个性化模式的价值呈现
人工智能