跳至主要内容

Mistral Large 3 教程:构建多模态智能评估中心

学习如何使用 Mistral Large 3 与 Streamlit,将图表和 PDF 转换为结构化表格、洞见及多语种摘要。
· 9分钟 阅读

使用 AI 探索

Mistral Large 3 是一款多模态 MoE 模型,旨在从复杂的视觉文档中读取、推理并结构化信息。它并不将图表和 PDF 视为静态图像,而是被设计用于从中提取表格、洞见,并生成打磨过的叙述。

在本教程中,我们将使用 Mistral Large 3(mistral-large-2512)模型在 Streamlit 中构建一个多模态智能评估中心。流程如下:

  • 您上传图表、财务报表或多页 PDF 等输入内容。
  • Mistral 直接从图像中读取文本与数字。
  • 它将可见表格重建为 CSV 字符串。
  • 随后,Mistral 基于输入生成洞见、风险与异常点。
  • 最后,它返回多语种的简短摘要。

以上全部仅需一个模型(mistral-large-2512)、一个端点(/v1/chat/completions),以及严格的 JSON Schema 来确保输出结构化、可依赖。

建议先阅读我们的Mistral 3 文章,了解这些新模型的关键特性。您还可以在另一篇教程中学习微调 Ministral 3。

什么是 Mistral Large 3?

Mistral Large 3 采用稀疏专家混合(MOE)架构,6750 亿总参数中有 410 亿为激活参数,并配备 25 亿参数的视觉编码器用于图像理解。

以下是与该模型相关的一些关键属性:

  • 多模态:Mistral Large 3 可处理文本与图像(图表、截图、文档)。
  • 长上下文(256k tokens):面向长文档、RAG 流水线与企业工作流。
  • 具代理性且支持结构化输出:通过聊天 API 支持函数调用与 JSON/JSON Schema 输出。
  • 多语言:Mistral 的 large 3 模型在 40 多种语言上表现出色,而不仅限于英语。

模型卡与生态系统文档还提到了一些重要注意事项:

  • 它不是专门的推理模型;也有某些专用推理变体在困难逻辑上可能胜过它。
  • 在纯视觉任务上,其排名不及视觉专用模型,因此预处理与优质提示很关键。
  • 对于视觉任务,Mistral 建议保持接近 1:1 的纵横比,避免极窄或极宽的图像。因此,裁剪图像有助于提升模型性能。 

Mistral 3 Large 示例项目:多模态智能评估中心

在本节中,我们将使用封装在 Streamlit 应用中的 Mistral Large 3 构建一个多模态智能评估中心。总体而言,最终应用将实现:

  • 接收类似报告的输入,如图表截图、单页报告或包含表格与图的多页 PDF。
  • 将 PDF 转换为一张纵向拼接图,然后按中心裁剪至近似 1:1 纵横比(遵循 Mistral Large 3 的视觉建议),调整大小至约 1024×1024,并将结果编码为 base64 data URL。
  • 随后,我们将用户指令与图像一并发送至 mistral-large-2512,使用 JSON Schema 模式下的 response_format 获取结构化 JSON 负载。
  • 最后,我们将 JSON 渲染为交互式仪表盘,包括以 DataFrame 呈现并可下载 CSV 的重建表格、要点式洞见/风险/异常、适合社媒发布的文案,以及以折叠面板呈现的多语种摘要。

这将把静态的可视化报告转化为完全由 Mistral Large 3 驱动的交互式智能助手。

Mistral 3 Large 教程效果

下面让我们一步一步构建。

步骤 1:先决条件与环境搭建

在基于 Mistral Large 3 搭建我们的 Streamlit 演示之前,需要一个基础的本地环境,通过 Mistral API 与模型通信。首先安装一些用于处理 UI、文档与配置的辅助库。

pip install streamlit mistralai pillow pdf2image pandas python-dotenv

我们将使用上述核心库:如streamlit 用于交互式 Web 应用 UI,mistralai 与 Mistral API 通信,pillow 与 pdf2image 用于打开并将 PDF 转换为图像,pandas 处理表格数据或日志,python-dotenv 用于从 .env 文件安全加载 MISTRAL_API_KEY 等环境变量。

接着,登录Mistral AI 平台并转到 API Keys 标签页。

Mistral AI 工作室

设置默认工作区(如有提示)并点击 Create new key。

Mistral API 密钥

为密钥起一个有意义的名称,并可选设置到期日期。

Mistral 新建 API 密钥

最后,复制生成的密钥,并如下导出为环境变量:

export MISTRAL_API_KEY="your_mistral_api_key_here"

至此,环境已可用于通过 Mistral API 进行身份验证。

步骤 2:初始化 Mistral 客户端

配置好环境并安装依赖后,下一步是初始化 Mistral 客户端,用于处理对 Mistral Large 3 模型的所有调用。这为我们从 Streamlit 应用向 Mistral Large 3 发送提示提供了入口。

MISTRAL_API_KEY = os.getenv("MISTRAL_API_KEY")
MODEL_ID = "mistral-large-2512"  
if MISTRAL_API_KEY is None:
    raise RuntimeError("Please set the MISTRAL_API_KEY environment variable.")
client = MistralClient(api_key=MISTRAL_API_KEY)

上述代码块完成三件事:

  • 从环境中读取 MISTRAL_API_KEY,并将模型名称存储在 MODEL_ID 中,便于全局复用。
  • 校验 API 密钥是否已设置,若缺失则抛出清晰错误。
  • 使用官方 mistralai SDK 构造 MistralClient 实例,后续所有请求均通过它发送。

客户端初始化完成后,我们将构建一些辅助函数,在 Streamlit UI 中发送提示并处理响应。

步骤 3:视觉预处理

在将任意图像或 PDF 发送给 Mistral Large 3 之前,需要让它们“更友好于模型”。当输入为方形、尺寸合适且编码一致时,视觉 API 的效果最佳。

步骤 3.1:中心裁剪

我们先加载上传的图像,并将其转换为方形裁剪。

def load_image_from_upload(uploaded_file) -> Image.Image:
    return Image.open(io.BytesIO(uploaded_file.read())).convert("RGB")
def center_crop_to_square(img: Image.Image) -> Image.Image:
    width, height = img.size
    if width == height:
        return img
    if width > height:
        offset = (width - height) // 2
        box = (offset, 0, offset + height, height)
    else:
        offset = (height - width) // 2
        box = (0, offset, width, offset + width)
    return img.crop(box)

上述代码块确保我们上传的图表或表格在发送给模型前被转换为方形裁剪,以符合 Mistral 的视觉指南。

步骤 3.2:调整尺寸

接着,将图像调整到最大尺寸(1024×1024),以平衡质量、延迟与 token 成本。

def resize_for_vlm(img: Image.Image, max_size: int = 1024) -> Image.Image:
    width, height = img.size
    scale = min(max_size / width, max_size / height, 1.0)
    if scale == 1.0:
        return img
    new_w = int(width * scale)
    new_h = int(height * scale)
    return img.resize((new_w, new_h), Image.LANCZOS)

此处我们计算 scale 因子,使两边都不超过 max_size,同时保持纵横比。若图像已足够小,则原样返回;否则计算新尺寸,并使用LANCZOS 进行高质量缩小。

此步骤确保图像不会占满上下文窗口或无谓拖慢响应。

步骤 3.3:PDF 转纵向拼接图

对于 PDF,我们常常希望让模型看到不止一页。一个简便方法是渲染所有页面并纵向拼接为一张长图。

def stack_images_vertically(images: List[Image.Image]) -> Image.Image:
    if not images:
        raise ValueError("No images to stack")
    target_width = images[0].size[0]
    resized_images = []
    for img in images:
        if img.size[0] != target_width:
            aspect_ratio = img.size[1] / img.size[0]
            new_height = int(target_width * aspect_ratio)
            img = img.resize((target_width, new_height), Image.LANCZOS)
        resized_images.append(img)
    total_height = sum(img.size[1] for img in resized_images)
    stacked = Image.new('RGB', (target_width, total_height))
    y_offset = 0
    for img in resized_images:
        stacked.paste(img, (0, y_offset))
        y_offset += img.size[1] 
    return stacked
def uploaded_file_to_square_base64(uploaded_file) -> Tuple[str, str]:
    mime_type = uploaded_file.type
    raw_bytes = uploaded_file.getvalue()
    if mime_type == "application/pdf":
        pages = convert_from_bytes(raw_bytes)
        pages_rgb = [page.convert("RGB") for page in pages]
        img = stack_images_vertically(pages_rgb)
        img = resize_for_vlm(img, max_size=1024)
        mime_type = "image/png"  
    else:
        img = Image.open(io.BytesIO(raw_bytes)).convert("RGB")
        img = center_crop_to_square(img)
        img = resize_for_vlm(img, max_size=1024)
    return mime_type, image_to_base64_data_url(img, mime_type=mime_type)

stack_images_vertically() 函数接收一组 PIL 图像,统一到相同宽度并保持纵横比,计算总高度,然后将它们依次粘贴到单一 RGB 画布上。而uploaded_file_to_square_base64() 函数则基于 MIME 类型进行分支处理:

  • 对于 PDF,将每页转换为图像,纵向拼接并调整大小。
  • 对于常规图像上传,加载图像、中心裁剪为方形并调整大小。

两种情况下都会返回一个标准化图像及其 MIME 类型,以便进行最终编码。

步骤 3.4:编码为 base64

Mistral 的视觉 API 接受 image_url 对象作为图像输入,既可以是实际 URL,也可以是 data: URL。

def image_to_base64_data_url(img: Image.Image, mime_type: str = "image/png") -> str:
    buffer = io.BytesIO()
    if mime_type == "image/jpeg":
        img.save(buffer, format="JPEG", quality=90)
    else:
        img.save(buffer, format="PNG")
        mime_type = "image/png"
    b64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
    return f"data:{mime_type};base64,{b64}"

我们先将图像写入内存缓冲区,并选择合适格式(如 JPEG 或 PNG),最后对字节进行 base64 编码。

最后将编码后的字符串包装为 data:{mime_type};base64 URL,可直接作为 image_url 传给 Mistral 视觉 API。

完成此步骤后,无论上传的是单张 PNG、宽幅仪表盘还是多页 PDF,都将经过中心裁剪、尺寸调整、PDF 拼接与 base64 data URL 转换的统一流程。

步骤 4:多模态推理调用

本步骤将一切封装到一个辅助函数中,发送视觉+文本请求,并强制模型以 JSON 格式响应。我们通过组合以下内容来实现:

  • 一个系统提示,将模型角色定义为多模态智能评估器,
  • 一个用户消息,混合了指令与图像内容,
  • 以及通过 response_format 传入的 JSON Schema,以确保输出结构化、易解析。
def call_mistral_large_multimodal(
    mime_type: str,
    image_data_url: str,
    user_instruction: str,
    languages: List[str],
) -> Dict[str, Any]:
    json_schema = {
        "type": "object",
        "properties": {
            "csv_tables": {
                "type": "array",
                "items": {"type": "string"},
                "description": "Each item is a CSV string representing one table found in the image."
            },
            "insights": {
                "type": "array",
                "items": {"type": "string"},
                "description": "Up to 5 key insights about the content."
            },
            "risks": {
                "type": "array",
                "items": {"type": "string"},
                "description": "Up to 5 key risks or red flags."
            },
            "anomalies": {
                "type": "array",
                "items": {"type": "string"},
                "description": "Any anomalies, outliers, or surprising patterns you detect."
            },
            "translations": {
                "type": "object",
                "properties": {
                    lang: {"type": "string"} for lang in languages
                },
                "description": "Short high-level summaries in the selected languages."
            },
        },
        "required": ["csv_tables", "insights", "risks"],
        "additionalProperties": False,
    }
    system_prompt = (
        "You are a Multimodal Intelligence Evaluator using Mistral Large 3.\n"
        "You are given a single document-like image (e.g. chart + table, financial report page).\n\n"
        "Your tasks:\n"
        "1. Read all visible text and numbers directly from the image.\n"
        "2. Reconstruct any clearly visible tables into valid CSV strings.\n"
        "   - Use the first row as headers when possible.\n"
        "   - Use commas as separators and newline per row.\n"
        "3. Derive up to 5 concise INSIGHTS about trends, patterns, or takeaways.\n"
        "4. Derive up to 5 concise RISKS or red flags (business, financial, operational, etc.).\n"
        "5. Detect any anomalies or surprising patterns if present (else return an empty list).\n"
        "6. Provide short summaries in the requested languages.\n\n"
        "You MUST respond ONLY with a JSON object that matches the provided JSON schema.\n"
        "Do not include any extra commentary outside of the JSON."
    )
    messages = [
        {
            "role": "system",
            "content": [
                {"type": "text", "text": system_prompt},
            ],
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": user_instruction or "Analyze this financial report page.",
                },
                {
                    "type": "image_url",
                    "image_url": image_data_url,
                },
            ],
        },
    ]
    response = client.chat(
        model=MODEL_ID,
        messages=messages,
        temperature=0.2,
        max_tokens=2048,
        response_format={
            "type": "json_schema",
            "json_schema": {
                "name": "multimodal_intel_eval",
                "schema": json_schema,
                "strict": True,
            },
        },
    )
    content = response.choices[0].message.content
    try:
        parsed = json.loads(content)
    except json.JSONDecodeError:
        try:
            start = content.index("{")
            end = content.rindex("}") + 1
            parsed = json.loads(content[start:end])
        except Exception:
            raise ValueError(f"Model did not return valid JSON. Raw content:\n{content}")
    return parsed

call_mistral_large_multimodal() 函数是多模态智能演示的核心引擎。

  • 函数首先构建一个 JSON Schema,定义 csv_tables、insights、risks、anomalies 的数组,以及基于 languages 列表键生成的 translations 对象。
  • 我们还定义了详细的系统提示,将模型定位为多模态智能评估器,并引导其完成读取数字、将表格重建为 CSV、提取洞见、风险、异常点以及多语种摘要等任务。
  • call_mistral_large_multimodal() 函数随后以聊天格式构建消息数组,将系统指令与同时包含文本指令和 image_url 载荷的用户消息结合,使用 MODEL_ID 调用 client.chat(),在低温度下获取更确定性的输出,并通过 response_format={"type": "json_schema",} 使响应符合架构。最后尝试通过 json.loads 解析。

有了它,您就拥有了将原始图表或报告图像转换为结构化叙事的稳健入口。

步骤 5:构建 Streamlit 界面

在预处理管线与 Mistral 客户端就位后,最后一步是将一切封装到 Streamlit 界面中。

st.set_page_config(
    page_title="Multimodal Intelligence Evaluation Hub - Mistral Large 3",
    layout="wide",
)
st.title("Multimodal Intelligence Evaluation Hub")
st.caption("Powered by **Mistral Large 3**")
col_left, col_right = st.columns([2, 1])
with col_left:
    uploaded_file = st.file_uploader(
        "Upload an image or report (PNG, JPG, WEBP, or PDF)",
        type=["png", "jpg", "jpeg", "webp", "pdf"],
    )
    default_prompt = (
        "Give me 3–5 key insights and risks from this input and export any tables you see as CSV "
        "Also write down the findings for a professional audience."
    )
    user_instruction = st.text_area(
        "Instruction to Mistral Large 3",
        value=default_prompt,
        height=120,
    )
with col_right:
    st.subheader("Multilingual Options")
    languages = st.multiselect(
        "Additional summary languages",
        options=["fr", "de", "es", "hi", "zh", "ja"],
        default=["hi"],
        help="Mistral Large 3 supports dozens of languages; these will receive short summaries.",
        label_visibility="collapsed",
    )
    run_button = st.button("Run Analysis", type="primary")
if run_button:
    if uploaded_file is None:
        st.error("Please upload an image or PDF first.")
        st.stop()
    prep_msg = "Preparing PDF (combining all pages)..." if uploaded_file.type == "application/pdf" else "Preparing image..."
    with st.spinner(prep_msg):
        mime_type, data_url = uploaded_file_to_square_base64(uploaded_file)
    mime, b64_part = data_url.split(",", 1)
    img_bytes = base64.b64decode(b64_part)
    st.image(img_bytes, caption="Center-cropped & resized image for the model", width=400)
    with st.spinner("Processing..."):
        try:
            result = call_mistral_large_multimodal(
                mime_type=mime_type,
                image_data_url=data_url,
                user_instruction=user_instruction,
                languages=languages,
            )
        except Exception as e:
            st.error(f"Error calling Mistral Large 3: {e}")
            st.stop()
    st.header("Results")
    csv_tables = result.get("csv_tables", [])
    if csv_tables:
        st.subheader("Reconstructed Tables (CSV)")
        for i, csv_str in enumerate(csv_tables):
            st.markdown(f"**Table {i+1}**")
            try:
                df = pd.read_csv(io.StringIO(csv_str))
                st.dataframe(df, use_container_width=True)
            except Exception:
                st.text_area(f"Raw CSV for Table {i+1}", value=csv_str, height=150)
            st.download_button(
                label=f"Download Table {i+1} as CSV",
                data=csv_str,
                file_name=f"table_{i+1}.csv",
                mime="text/csv",
                key=f"csv_download_{i}",
            )
    else:
        st.info("No tables were detected or reconstructed.")
    insights = result.get("insights", [])
    risks = result.get("risks", [])
    anomalies = result.get("anomalies", [])
    col_ins, col_risk = st.columns(2)
    with col_ins:
        st.subheader("Key Insights")
        if insights:
            for bullet in insights:
                st.markdown(f"- {bullet}")
        else:
            st.write("_No explicit insights returned._")
    with col_risk:
        st.subheader("Risks")
        if risks:
            for bullet in risks:
                st.markdown(f"- {bullet}")
        else:
            st.write("_No explicit risks returned._")
    st.subheader("Anomalies")
    if anomalies:
        for bullet in anomalies:
            st.markdown(f"- {bullet}")
    else:
        st.write("_No anomalies reported._")
    translations = result.get("translations", {}) or {}
    if translations:
        st.subheader(" Multilingual Summaries")
        for lang_code, summary in translations.items():
            with st.expander(f"Summary in {lang_code}"):
                st.write(summary)
    else:
        st.info("No multilingual summaries were requested or returned.")
    # Raw JSON (debug)
    # with st.expander(" Raw JSON (debug)"):
    #     st.json(result)

上述 StreamLit UI 代码完成了几项关键工作:

st.set_page_config 将布局分为两列。左侧用于核心输入(文件上传器与带默认指令的可编辑文本区),右侧通过 st.multiselect 提供多语言选项及一个主要Run Analysis 按钮。点击按钮后,应用会校验文件是否存在,同时将图像或 PDF 转换为 base64 data URL,并展示预览图像。

结果返回后,会将重建的 CSV 表格以数据框显示,同时呈现洞见、风险、异常及多语种摘要。我们还可启用调试视图(已注释),以便在开发过程中检查原始 JSON。

完成此步骤后,您可将全部内容保存为 app.py,并通过以下命令启动完整体验:

streamlit run app.py

在下方视频中,您可以看到图像与 PDF 输入的精简版工作流演示:

结语

在本教程中,我们将 Mistral Large 3 从通用多模态模型,打造成一个聚焦的视觉智能助手。通过少量视觉预处理、严格的 JSON Schema 与 Streamlit UI,我们构建了一个中心枢纽,可将静态图表与 PDF 转换为结构化表格、洞见、风险、异常与多语种摘要。

尽管演示以图表与 PDF 为中心,但同样适用于发票、收据、学术论文、财报以及其他高密度视觉文档。

接下来,您可以扩展该中心,加入文本与视觉分析、跨文档趋势摘要,或将其接入更大的 RAG。借助少量额外工具与提示,即可将其升级为完整的代理式报告系统。

如果您希望继续深入学习,推荐以下资源:

FAQs

为何不使用 Mistral 的 Document AI / OCR 堆栈?

官方文档建议在 OCR 负载较重的场景使用专门的 Document AI 模型,但在此我们有意从端到端对通用的 Mistral Large 3 模型进行了压力测试。

为什么要裁剪为 1:1 纵横比?

Mistral Large 3 文档与社区评测指出,其视觉编码器在接近方形的图像上表现最佳,对极窄或极宽的纵横比可能吃力。裁剪为 1:1 并调整尺寸是一种高杠杆的预处理步骤,可提升一致性。

我之后能替换为其他模型吗?

可以。只要模型支持以下能力,该架构便与模型无关:

  • 图像输入(image_url)
  • JSON 或 JSON Schema 输出
  • 足够的上下文以处理不同文档尺寸

您可以通过兼容 OpenAI 的 API 以极少改动接入其他多模态模型。

我的 PDF 或图像可以有多大?

从实用角度看,图像或 PDF 的大小受以下因素限制:

  • 模型的上下文窗口(最多 256k tokens)
  • 图像分辨率与质量 vs. token 成本
  • Streamlit 与 pdf2image 的性能

对于超大文档,可考虑:

  • 仅限关键页面
  • 拆分为块并多次处理
  • 必要时结合基于文本的 OCR 与 Mistral 进行推理
Aashi Dutt

作者

Aashi Dutt

我是一名 Google Developers 机器学习(生成式 AI)领域的专家、Kaggle 三项专家,以及 Women Techmakers 大使,拥有 3 年以上的技术从业经验。2020 年我共同创办了一家健康科技初创公司,目前在佐治亚理工学院攻读计算机科学硕士,专攻机器学习。

主题