<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://vane.astrovela.ai/zh-CN/blog</id>
    <title>Vane Blog</title>
    <updated>2026-08-31T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://vane.astrovela.ai/zh-CN/blog"/>
    <subtitle>Vane Blog</subtitle>
    <icon>https://vane.astrovela.ai/zh-CN/favicon.svg</icon>
    <entry>
        <title type="html"><![CDATA[Vane Data：从多模态文件到可查询数据]]></title>
        <id>https://vane.astrovela.ai/zh-CN/blog/from-files-to-queryable-data</id>
        <link href="https://vane.astrovela.ai/zh-CN/blog/from-files-to-queryable-data"/>
        <updated>2026-08-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Vane Data 如何用 SQL、Python UDF、AI Function 与 GPU Actor，把 PDF、图片、音频和视频组织成可查询的 Relation。]]></summary>
        <content type="html"><![CDATA[<p class="blog-body-lead">AI 应用往往需要处理 PDF、图片、音频和视频。进入数据管道前，需要对 PDF 拆页、对图片解码、对音频重采样，并沿视频时间轴展开，同时保留文件名、页码和帧号等定位信息。本文以这四类文件为例，介绍 Vane Data 如何以 Relation（多模态数据集）为核心，串联文件展开、批量处理、模型推理、查询和写出，把原始文件转化为可查询的数据。</p>
<div class="callout"><span class="cb">阅读说明<!-- -->.</span><p>本文用概念性伪代码说明 Vane Data 如何组织多模态处理流程，省略 PyMuPDF、图片解码、Whisper 和 YOLO 等工具的具体实现。文末列出了相关的可运行示例。</p></div>
<img class="dimg" style="width:100%;height:auto" src="https://vane.astrovela.ai/img/blog/from-files-to-queryable-data/vane-data-multimodal-pipeline-zh-cn.png" alt="Vane Data 将 PDF、图片、音频和视频处理为可查询数据" width="1800" height="766" loading="lazy" decoding="async">
<h2 class="anchor anchorTargetStickyNavbar_Vzrq ds" id="sql-与-python两种-api一条数据管道">SQL 与 Python：两种 API，一条数据管道</h2>
<p>在 Vane Data 中，文件内容、业务字段和处理结果都保存在 <strong>Relation</strong> 里。SQL 适合表达字段计算、筛选和 AI Function 调用；Python API 适合接入现有处理库、执行一对多展开，以及配置 Actor 和 GPU 等运行资源。</p>
<p>解析、解码和格式转换可以接入<strong>无状态 UDF</strong>；Whisper、YOLO 等不宜反复加载的模型则封装成<strong>有状态的 UDF</strong>，由 Actor 初始化一次，再连续处理多个批次。Prompt 和 Embedding 交给 <strong>AI Function。</strong></p>
<p>下面四个示例分别展示 PDF、图片、音频和视频如何从原始文件变成可查询的 Relation，并概览各自的处理流程、主要产出和代码组织方式。</p>
<table class="dt"><thead><tr><th>示例</th><th>典型处理流程</th><th>主要产出</th><th>代码呈现</th></tr></thead><tbody><tr><td>PDF</td><td>PDF → 文本块  → Embedding / 语义字段</td><td>每行一个文本块，保留来源、页码、块序号和文本，并增加 <span class="link">embedding</span>、<span class="link">topics</span>、<span class="link">chunk_summary</span></td><td>SQL 读取文件并生成向量与语义字段；  <br>Python <span class="link">flat_map</span> 展开页面和文本块</td></tr><tr><td>图片</td><td>图片 BLOB → 批量解码与检查 → 可用图片 → 视觉 Prompt → STRUCT 字段</td><td>每行一张通过检查的图片，包含文件信息、宽高、中文摘要和模型自报置信度</td><td>SQL 调用 <span class="link">inspect_image</span> 完成检查与筛选，再用 <span class="link">AI_PROMPT</span> 生成结构化描述；  <br>Python 注册 UDF</td></tr><tr><td>音频</td><td>音频 bytes → 解码与 16 kHz 重采样 → Whisper 输入特征 → 模型推理 → 转写文本</td><td>每行一条音频，保留路径、语言和业务字段，并增加中文 <span class="link">transcription</span></td><td>SQL CTE 串联批处理 UDF；  <br>Python 注册无状态 UDF 和复用 Whisper 模型的 Actor</td></tr><tr><td>视频</td><td>视频文件 → 帧  → 每帧检测结果 → 目标 → 裁剪图片</td><td>每行一个检测目标，包含视频与帧定位信息、类别、置信度、边界框和裁剪后的 PNG BLOB</td><td>Python 使用 <span class="link">VideoFrameSource</span>、<span class="link">map_batches</span> 和 GPU Actor 完成帧读取、检测、目标展开与裁剪</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq ds" id="pdf拆成文本块生成检索与语义字段">PDF：拆成文本块，生成检索与语义字段</h2>
<p><strong>典型流程：</strong><span class="link">PDF → 页面 → 文本块 → Embedding / 语义字段</span></p>
<p>PDF 案例处理的是一批文本型文档，目标是得到可检索的文本块。每个文本块都带有文件来源、页码和块序号，检索结果可以直接回到原文；向量用于相似度检索，主题和摘要则用于筛选和结果展示。</p>
<p>示例通过 <span class="link">read_blob</span> 读入文件，PyMuPDF 按页提取文本，两次 Python <span class="link">flat_map</span> 分别展开页面和文本块。切块大小与 Embedding 模型的 tokenizer 对齐并保留适度重叠，再通过 SQL <span class="link">AI_EMBED</span> 生成向量字段。</p>
<p>Embedding 和 Prompt 可以处理同一份文本块 Relation，<span class="link">embedding</span> 用于相似度检索，<span class="link">topics</span> 和 <span class="link">chunk_summary</span> 用于筛选和结果展示。</p>
<div class="term"><div class="term-bar"><span class="sq sq1"></span><span class="sq sq2"></span><span class="sq sq3"></span><span class="fn">example.py</span><button type="button" class="term-copy" aria-label="复制代码">复制</button></div><pre class="code"><span class="token-line" style="color:#15171E"><span class="token keyword" style="color:#8E3DA8;font-weight:bold">import</span><span class="token plain"> vane</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 以下占位函数分别代表 PyMuPDF 和基于 tokenizer 的辅助逻辑。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 完整的 PDF 处理流程请参考文末链接的可运行示例。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">EMBEDDING_MODEL </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> </span><span class="token string" style="color:#3F8A3C">"sentence-transformers/all-MiniLM-L6-v2"</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">MAX_CHUNK_TOKENS </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> </span><span class="token number" style="color:#A86420">240</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">CHUNK_TOKEN_OVERLAP </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> </span><span class="token number" style="color:#A86420">32</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># flat_map 回调：接收一条 PDF 记录，按页生成多条记录。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">def</span><span class="token plain"> </span><span class="token function" style="color:#2E66C4">extract_pages</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">row</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">for</span><span class="token plain"> page_number</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> text </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">in</span><span class="token plain"> parse_pdf</span><span class="token punctuation" style="color:#595D67">(</span><span class="token builtin" style="color:#1A7E7B">bytes</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">row</span><span class="token punctuation" style="color:#595D67">[</span><span class="token string" style="color:#3F8A3C">"content"</span><span class="token punctuation" style="color:#595D67">]</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">yield</span><span class="token plain"> </span><span class="token punctuation" style="color:#595D67">{</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            </span><span class="token string" style="color:#3F8A3C">"source"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> row</span><span class="token punctuation" style="color:#595D67">[</span><span class="token string" style="color:#3F8A3C">"source"</span><span class="token punctuation" style="color:#595D67">]</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            </span><span class="token string" style="color:#3F8A3C">"size"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> row</span><span class="token punctuation" style="color:#595D67">[</span><span class="token string" style="color:#3F8A3C">"size"</span><span class="token punctuation" style="color:#595D67">]</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            </span><span class="token string" style="color:#3F8A3C">"page_number"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> page_number</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            </span><span class="token string" style="color:#3F8A3C">"text"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> text</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token punctuation" style="color:#595D67">}</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># flat_map 回调：接收一条页面记录，将其切分为按 tokenizer 对齐且相互重叠的文本块。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">def</span><span class="token plain"> </span><span class="token function" style="color:#2E66C4">split_chunks</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">row</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">for</span><span class="token plain"> chunk_index</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> text </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">in</span><span class="token plain"> </span><span class="token builtin" style="color:#1A7E7B">enumerate</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        split_text_by_tokens</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            row</span><span class="token punctuation" style="color:#595D67">[</span><span class="token string" style="color:#3F8A3C">"text"</span><span class="token punctuation" style="color:#595D67">]</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            model</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">EMBEDDING_MODEL</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            max_tokens</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">MAX_CHUNK_TOKENS</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            overlap_tokens</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">CHUNK_TOKEN_OVERLAP</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">yield</span><span class="token plain"> </span><span class="token punctuation" style="color:#595D67">{</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            </span><span class="token string" style="color:#3F8A3C">"source"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> row</span><span class="token punctuation" style="color:#595D67">[</span><span class="token string" style="color:#3F8A3C">"source"</span><span class="token punctuation" style="color:#595D67">]</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            </span><span class="token string" style="color:#3F8A3C">"size"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> row</span><span class="token punctuation" style="color:#595D67">[</span><span class="token string" style="color:#3F8A3C">"size"</span><span class="token punctuation" style="color:#595D67">]</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            </span><span class="token string" style="color:#3F8A3C">"page_number"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> row</span><span class="token punctuation" style="color:#595D67">[</span><span class="token string" style="color:#3F8A3C">"page_number"</span><span class="token punctuation" style="color:#595D67">]</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            </span><span class="token string" style="color:#3F8A3C">"chunk_index"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> chunk_index</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            </span><span class="token string" style="color:#3F8A3C">"text"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> text</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token punctuation" style="color:#595D67">}</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># flat_map 会改变行数，因此每个阶段都需要声明输出 schema。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">PAGE_SCHEMA </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#595D67">{</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token string" style="color:#3F8A3C">"source"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sqltypes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">VARCHAR</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token string" style="color:#3F8A3C">"size"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sqltypes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">BIGINT</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token string" style="color:#3F8A3C">"page_number"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sqltypes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">INTEGER</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token string" style="color:#3F8A3C">"text"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sqltypes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">VARCHAR</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">}</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">CHUNK_SCHEMA </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#595D67">{</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token operator" style="color:#595D67">**</span><span class="token plain">PAGE_SCHEMA</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token string" style="color:#3F8A3C">"chunk_index"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sqltypes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">INTEGER</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">}</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 同一个连接既执行 SQL 查询，也承接后续的 Python Relation 操作。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">con </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">connect</span><span class="token punctuation" style="color:#595D67">(</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># read_blob 将文件元数据和二进制内容读入同一个 Relation。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">pdfs </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> con</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sql</span><span class="token punctuation" style="color:#595D67">(</span><span class="token triple-quoted-string string" style="color:#3F8A3C">"""</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    SELECT filename AS source, size, content</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    FROM read_blob('/data/pdfs/*.pdf')</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">"""</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 第一次 flat_map 将一条 PDF 记录展开为多条页面记录。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">pages </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> pdfs</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">flat_map</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">extract_pages</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> schema</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">PAGE_SCHEMA</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 第二次 flat_map 将一条页面记录展开为多条文本块记录。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">chunks </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> pages</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">flat_map</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">split_chunks</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> schema</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">CHUNK_SCHEMA</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 文本块形成 Relation 后切回 SQL，通过 AI_EMBED 增加向量字段。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">embedded </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> chunks</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">query</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token string" style="color:#3F8A3C">"chunks"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token triple-quoted-string string" style="color:#3F8A3C">"""</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    SELECT</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        *,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        -- 为每个文本块生成向量，同时保留定位字段。</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        AI_EMBED(</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            text,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            provider := 'transformers',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            model := 'sentence-transformers/all-MiniLM-L6-v2',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            options := struct_pack(</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                device := 'cpu',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                batch_size := 32</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            )</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        ) AS embedding</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    FROM chunks</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    """</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 在同一个 Relation 上继续调用 AI_PROMPT，生成可查询的语义字段。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">result </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> embedded</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">query</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token string" style="color:#3F8A3C">"embedded"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token triple-quoted-string string" style="color:#3F8A3C">"""</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    WITH enriched AS (</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        SELECT</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            *,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            -- AI_PROMPT 返回 STRUCT(topics, summary)，失败时返回 NULL。</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            AI_PROMPT(</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                text,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                return_format := json '{</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                    "type": "object",</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                    "properties": {</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                        "topics": {</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                            "type": "array",</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                            "items": {"type": "string"}</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                        },</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                        "summary": {"type": "string"}</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                    },</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                    "required": ["topics", "summary"],</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                    "additionalProperties": false</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                }',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                system_message :=</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                    'Extract the main topics from the text chunk and summarize it in one sentence.',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                provider := 'vllm',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                model := 'Qwen/Qwen2.5-7B-Instruct',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                on_error := 'ignore',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                options := struct_pack(</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                    max_tokens := 128,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                    temperature := 0.0</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                )</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            ) AS metadata</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        FROM embedded</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    )</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    SELECT</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        source,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        size,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        page_number,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        chunk_index,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        text,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        embedding,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        -- 直接展开 STRUCT，供后续 SQL 筛选和展示。</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        metadata.topics AS topics,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        metadata.summary AS chunk_summary</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    FROM enriched</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    """</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># write_parquet 触发整条 Relation 流水线执行。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">result</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">write_parquet</span><span class="token punctuation" style="color:#595D67">(</span><span class="token string" style="color:#3F8A3C">"/tmp/pdf_chunks_enriched.parquet"</span><span class="token punctuation" style="color:#595D67">)</span></span></pre></div>
<p><span class="link">write_parquet</span> 触发整条 Relation 执行。示例仅覆盖文本型 PDF；扫描件、密码保护或损坏文件需要另行分流。</p>
<p>输出中每行对应一个文本块，字段如下：</p>
<table class="dt"><thead><tr><th>字段</th><th>类型</th><th>用途</th></tr></thead><tbody><tr><td><span class="link">source</span></td><td><span class="link">VARCHAR</span></td><td>定位原始 PDF</td></tr><tr><td><span class="link">size</span></td><td><span class="link">BIGINT</span></td><td>保留原始文件大小</td></tr><tr><td><span class="link">page_number</span></td><td><span class="link">INTEGER</span></td><td>定位原始页面</td></tr><tr><td><span class="link">chunk_index</span></td><td><span class="link">INTEGER</span></td><td>表示文本块在页面内的顺序</td></tr><tr><td><span class="link">text</span></td><td><span class="link">VARCHAR</span></td><td>保存送入模型的文本</td></tr><tr><td><span class="link">embedding</span></td><td><span class="link">FLOAT[384]</span></td><td>供下游向量索引和相似度查询使用</td></tr><tr><td><span class="link">topics</span></td><td><span class="link">VARCHAR[]</span></td><td>用于筛选文本块的主题列表</td></tr><tr><td><span class="link">chunk_summary</span></td><td><span class="link">VARCHAR</span></td><td>用于结果展示的文本块摘要</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq ds" id="图片筛选可用图片生成结构化描述">图片：筛选可用图片，生成结构化描述</h2>
<p><strong>典型流程：</strong><span class="link">图片 BLOB → 批量解码与检查 → 可用图片 → 视觉 Prompt → STRUCT 字段</span></p>
<p>图片部分关注结构化描述。图片检查逻辑由 Python 实现，会预先注册为 SQL UDF。SQL 调用该函数得到包含宽度、高度和 <span class="link">is_usable</span> 标记的 <span class="link">STRUCT</span>，再通过 <span class="link">WHERE</span> 分流图片。检查失败的记录可以形成单独的 Relation；通过检查的图片继续交给视觉模型。<span class="link">AI_PROMPT</span> 按 schema 生成 <span class="link">STRUCT(summary, model_confidence)</span>，SQL 负责约束字段和类型。</p>
<p>示例处理五张图片，并通过 OpenAI Provider 调用 <span class="link">gpt-4o-mini</span>。代码中的 <span class="link">inspect_image</span> 定义批量 UDF 的输入输出约定，底层图片解码逻辑由 <span class="link">inspect_image_blobs</span> 实现。</p>
<div class="term"><div class="term-bar"><span class="sq sq1"></span><span class="sq sq2"></span><span class="sq sq3"></span><span class="fn">example.py</span><button type="button" class="term-copy" aria-label="复制代码">复制</button></div><pre class="code"><span class="token-line" style="color:#15171E"><span class="token keyword" style="color:#8E3DA8;font-weight:bold">import</span><span class="token plain"> pyarrow </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">as</span><span class="token plain"> pa</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">import</span><span class="token plain"> vane</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># UDF 为每张图片返回一个 STRUCT，三个字段都可直接在 SQL 中访问。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">INSPECTION_TYPE </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> pa</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">struct</span><span class="token punctuation" style="color:#595D67">(</span><span class="token punctuation" style="color:#595D67">[</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    pa</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">field</span><span class="token punctuation" style="color:#595D67">(</span><span class="token string" style="color:#3F8A3C">"width"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> pa</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">int32</span><span class="token punctuation" style="color:#595D67">(</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    pa</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">field</span><span class="token punctuation" style="color:#595D67">(</span><span class="token string" style="color:#3F8A3C">"height"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> pa</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">int32</span><span class="token punctuation" style="color:#595D67">(</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    pa</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">field</span><span class="token punctuation" style="color:#595D67">(</span><span class="token string" style="color:#3F8A3C">"is_usable"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> pa</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">bool_</span><span class="token punctuation" style="color:#595D67">(</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">]</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 定义无状态批量 UDF；Vane 每批传入两个 Arrow Array。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 函数返回等长的 StructArray，batch_size 控制单批大小。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:#595D67">@vane</span><span class="token decorator annotation punctuation" style="color:#595D67">.</span><span class="token decorator annotation punctuation" style="color:#595D67">func</span><span class="token decorator annotation punctuation" style="color:#595D67">.</span><span class="token decorator annotation punctuation" style="color:#595D67">batch</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">return_dtype</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">INSPECTION_TYPE</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> batch_size</span><span class="token operator" style="color:#595D67">=</span><span class="token number" style="color:#A86420">32</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">def</span><span class="token plain"> </span><span class="token function" style="color:#2E66C4">inspect_image</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">image</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> minimum_side</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">return</span><span class="token plain"> inspect_image_blobs</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">image</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> minimum_side</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">con </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">connect</span><span class="token punctuation" style="color:#595D67">(</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 将 Python UDF 注册到当前连接，SQL 函数名为 inspect_image。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># parameters 声明函数签名为 (BLOB, INTEGER)。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">attach_function</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    inspect_image</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    alias</span><span class="token operator" style="color:#595D67">=</span><span class="token string" style="color:#3F8A3C">"inspect_image"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    connection</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">con</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    parameters</span><span class="token operator" style="color:#595D67">=</span><span class="token punctuation" style="color:#595D67">[</span><span class="token string" style="color:#3F8A3C">"BLOB"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> </span><span class="token string" style="color:#3F8A3C">"INTEGER"</span><span class="token punctuation" style="color:#595D67">]</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 将图片文件读入包含元数据和 BLOB 值的 Relation。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">images </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> con</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sql</span><span class="token punctuation" style="color:#595D67">(</span><span class="token triple-quoted-string string" style="color:#3F8A3C">"""</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    SELECT filename, size, content AS image</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    FROM read_blob('/data/images/*')</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">"""</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 在 SQL 投影中调用已注册的 Python UDF；64 表示最短边阈值。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">inspected </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> images</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">query</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token string" style="color:#3F8A3C">"images"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token triple-quoted-string string" style="color:#3F8A3C">"""</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    SELECT</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        *,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        -- 每行得到一个 STRUCT(width, height, is_usable)。</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        inspect_image(image, 64) AS inspection</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    FROM images</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    """</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 在 SQL 中按 is_usable 分流，只把可用图片交给模型。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">result </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> inspected</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">query</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token string" style="color:#3F8A3C">"inspected"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token triple-quoted-string string" style="color:#3F8A3C">"""</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    WITH described AS (</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        SELECT</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            filename,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            size,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            inspection.width AS width,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            inspection.height AS height,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            -- 对通过检查的图片调用视觉 Prompt，并要求返回固定结构的 STRUCT。</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            AI_PROMPT(</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                'Describe the main subject in the image and provide a confidence score from 0 to 1.',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                image,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                return_format := json '{</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                    "type": "object",</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                    "properties": {</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                        "summary": {"type": "string"},</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                        "model_confidence": {"type": "number"}</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                    },</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                    "required": ["summary", "model_confidence"],</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                    "additionalProperties": false</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                }',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                system_message := 'Return only a Chinese-language result that matches the requested structure.',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                provider := 'openai',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                model := 'gpt-4o-mini',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                on_error := 'ignore',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                options := struct_pack(</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                    use_chat_completions := true,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                    max_output_tokens := 128,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                    temperature := 0.0</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                )</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            ) AS answer</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        FROM inspected</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        WHERE inspection.is_usable</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    )</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    SELECT</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        filename,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        size,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        width,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        height,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        -- 将模型返回的 STRUCT 展开为普通的可查询列。</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        answer.summary AS summary,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        answer.model_confidence AS model_confidence</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    FROM described</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    """</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 写出时执行前面的 UDF 检查和模型调用。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">result</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">write_parquet</span><span class="token punctuation" style="color:#595D67">(</span><span class="token string" style="color:#3F8A3C">"/tmp/image_descriptions.parquet"</span><span class="token punctuation" style="color:#595D67">)</span></span></pre></div>
<p>五张图片的运行结果如下：</p>
<table class="dt"><thead><tr><th>文件名（<span class="link">filename</span>）</th><th>内容摘要（<span class="link">summary</span>）</th><th>模型自报置信度（<span class="link">model_confidence</span>）</th></tr></thead><tbody><tr><td><span class="link">n02094114_4707.JPEG</span></td><td>一只毛茸茸的小狗正在草地上奔跑</td><td>0.95</td></tr><tr><td><span class="link">n02398521_13903.JPEG</span></td><td><span class="link">NULL</span></td><td><span class="link">NULL</span></td></tr><tr><td><span class="link">n01784675_1352.JPEG</span></td><td>蜈蚣的特写画面，可以看到分节的身体和细长的足</td><td>0.95</td></tr><tr><td><span class="link">n02790996_10925.JPEG</span></td><td>一名男子正在健身房进行卧推</td><td>0.95</td></tr><tr><td><span class="link">n02018207_15713.JPEG</span></td><td><span class="link">NULL</span></td><td><span class="link">NULL</span></td></tr></tbody></table>
<p><span class="link">NULL</span> 表示图片通过了解码和尺寸检查，但 Prompt 阶段没有生成有效的结构化结果。原因可能是 Provider 调用失败，也可能是输出未通过校验；它不表示图片不可用。<span class="link">model_confidence</span> 可用于辅助排序。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq ds" id="音频分阶段转写并复用-whisper-模型">音频：分阶段转写并复用 Whisper 模型</h2>
<p><strong>典型流程：</strong><span class="link">音频 bytes → 解码与 16 kHz 重采样 → Whisper 输入特征 → 模型推理 → 转写文本</span></p>
<p>这里以普通话音频为例。音频样本的编码格式、采样率和时长并不统一，送入 Whisper 前需要先解码并重采样为 16 kHz。查询通过一组 SQL CTE 和最终投影显式展开各个阶段：先提取音频 BLOB，再依次完成解码与重采样、输入特征生成、Whisper 推理，以及把输出 token 解码成中文文本；后四个阶段分别调用已经注册的批处理 UDF。模型推理时显式指定 <span class="link">language="zh"</span> 和 <span class="link">task="transcribe"</span>，避免短音频依赖自动语言识别。</p>
<p>解码、特征生成和 token 解码由无状态批量 UDF 完成；<span class="link">WhisperTranscriber</span> Actor 为 <span class="link">whisper_transcribe_zh</span> SQL UDF 提供推理能力，并在生命周期内复用同一模型。SQL 负责串联各个阶段。</p>
<div class="term"><div class="term-bar"><span class="sq sq1"></span><span class="sq sq2"></span><span class="sq sq3"></span><span class="fn">example.py</span><button type="button" class="term-copy" aria-label="复制代码">复制</button></div><pre class="code"><span class="token-line" style="color:#15171E"><span class="token keyword" style="color:#8E3DA8;font-weight:bold">import</span><span class="token plain"> vane</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">MODEL_ID </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> </span><span class="token string" style="color:#3F8A3C">"openai/whisper-tiny"</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">SAMPLING_RATE </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> </span><span class="token number" style="color:#A86420">16000</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">BATCH_SIZE </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> </span><span class="token number" style="color:#A86420">128</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">NUM_GPU_ACTORS </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> </span><span class="token number" style="color:#A86420">1</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 声明三个中间结果的类型，同时用于 UDF 返回值和 SQL 参数。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">FEATURE_MELS </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> </span><span class="token number" style="color:#A86420">80</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">FEATURE_FRAMES </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> </span><span class="token number" style="color:#A86420">3000</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">RESAMPLED_AUDIO_TYPE </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">list_type</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sqltypes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">FLOAT</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">INPUT_FEATURES_TYPE </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">tensor_type</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sqltypes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">FLOAT</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">FEATURE_MELS</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> FEATURE_FRAMES</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">TOKEN_IDS_TYPE </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">list_type</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sqltypes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">INTEGER</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 以下占位函数分别代表音频解码、特征构建和 token 解码。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 完整的音频处理流程请参考文末链接的可运行示例。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 第一个无状态批量 UDF：将不同格式的音频 BLOB 解码并重采样为 16 kHz 波形。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:#595D67">@vane</span><span class="token decorator annotation punctuation" style="color:#595D67">.</span><span class="token decorator annotation punctuation" style="color:#595D67">func</span><span class="token decorator annotation punctuation" style="color:#595D67">.</span><span class="token decorator annotation punctuation" style="color:#595D67">batch</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">return_dtype</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">RESAMPLED_AUDIO_TYPE</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> batch_size</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">BATCH_SIZE</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">def</span><span class="token plain"> </span><span class="token function" style="color:#2E66C4">decode_resample_16k</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">audio_bytes</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">return</span><span class="token plain"> decode_and_resample</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">audio_bytes</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> sample_rate</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">SAMPLING_RATE</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 第二个无状态批量 UDF：将波形转换为 Whisper 要求的定长特征。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:#595D67">@vane</span><span class="token decorator annotation punctuation" style="color:#595D67">.</span><span class="token decorator annotation punctuation" style="color:#595D67">func</span><span class="token decorator annotation punctuation" style="color:#595D67">.</span><span class="token decorator annotation punctuation" style="color:#595D67">batch</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">return_dtype</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">INPUT_FEATURES_TYPE</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> batch_size</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">BATCH_SIZE</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">def</span><span class="token plain"> </span><span class="token function" style="color:#2E66C4">prepare_whisper_features</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">waveform</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">return</span><span class="token plain"> build_whisper_features</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        waveform</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        model</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">MODEL_ID</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        sample_rate</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">SAMPLING_RATE</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 定义有状态 Actor UDF；每个 Actor 使用一张 GPU，并连续处理多个批次。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:#595D67">@vane</span><span class="token decorator annotation punctuation" style="color:#595D67">.</span><span class="token decorator annotation punctuation" style="color:#595D67">cls</span><span class="token decorator annotation punctuation" style="color:#595D67">.</span><span class="token decorator annotation punctuation" style="color:#595D67">batch</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    actor_number</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">NUM_GPU_ACTORS</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    gpus</span><span class="token operator" style="color:#595D67">=</span><span class="token number" style="color:#A86420">1.0</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    return_dtype</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">TOKEN_IDS_TYPE</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    batch_size</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">BATCH_SIZE</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">class</span><span class="token plain"> </span><span class="token class-name" style="color:#1A7E7B">WhisperTranscriber</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">def</span><span class="token plain"> </span><span class="token function" style="color:#2E66C4">__init__</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">self</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token comment" style="color:#9A9DA6;font-style:italic"># Actor 创建时只执行一次 __init__，避免每批重复加载模型。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        self</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">model </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> load_whisper</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">MODEL_ID</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> device</span><span class="token operator" style="color:#595D67">=</span><span class="token string" style="color:#3F8A3C">"cuda"</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">def</span><span class="token plain"> </span><span class="token function" style="color:#2E66C4">__call__</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">self</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> input_features</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token comment" style="color:#9A9DA6;font-style:italic"># __call__ 处理一个特征批次，并显式指定中文转写。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">return</span><span class="token plain"> transcribe_to_token_ids</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            input_features</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            model</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">self</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">model</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            language</span><span class="token operator" style="color:#595D67">=</span><span class="token string" style="color:#3F8A3C">"zh"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            task</span><span class="token operator" style="color:#595D67">=</span><span class="token string" style="color:#3F8A3C">"transcribe"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 第三个无状态批量 UDF：将模型输出的 token ID 解码为中文字符串。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:#595D67">@vane</span><span class="token decorator annotation punctuation" style="color:#595D67">.</span><span class="token decorator annotation punctuation" style="color:#595D67">func</span><span class="token decorator annotation punctuation" style="color:#595D67">.</span><span class="token decorator annotation punctuation" style="color:#595D67">batch</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">return_dtype</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sqltypes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">VARCHAR</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> batch_size</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">BATCH_SIZE</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">def</span><span class="token plain"> </span><span class="token function" style="color:#2E66C4">decode_whisper_tokens</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">token_ids</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">return</span><span class="token plain"> decode_token_ids</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">token_ids</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> model</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">MODEL_ID</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">con </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">connect</span><span class="token punctuation" style="color:#595D67">(</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 注册解码与重采样 UDF，对应 SQL 签名 decode_resample_16k(BLOB)。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">attach_function</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    decode_resample_16k</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    alias</span><span class="token operator" style="color:#595D67">=</span><span class="token string" style="color:#3F8A3C">"decode_resample_16k"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    connection</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">con</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    parameters</span><span class="token operator" style="color:#595D67">=</span><span class="token punctuation" style="color:#595D67">[</span><span class="token string" style="color:#3F8A3C">"BLOB"</span><span class="token punctuation" style="color:#595D67">]</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 注册特征生成 UDF，其输入类型与上一阶段的返回类型一致。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">attach_function</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    prepare_whisper_features</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    alias</span><span class="token operator" style="color:#595D67">=</span><span class="token string" style="color:#3F8A3C">"prepare_whisper_features"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    connection</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">con</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    parameters</span><span class="token operator" style="color:#595D67">=</span><span class="token punctuation" style="color:#595D67">[</span><span class="token plain">RESAMPLED_AUDIO_TYPE</span><span class="token punctuation" style="color:#595D67">]</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 注册 Actor 实例；后续 SQL 调用都会复用已经加载的模型。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">attach_function</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    WhisperTranscriber</span><span class="token punctuation" style="color:#595D67">(</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    alias</span><span class="token operator" style="color:#595D67">=</span><span class="token string" style="color:#3F8A3C">"whisper_transcribe_zh"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    connection</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">con</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    parameters</span><span class="token operator" style="color:#595D67">=</span><span class="token punctuation" style="color:#595D67">[</span><span class="token plain">INPUT_FEATURES_TYPE</span><span class="token punctuation" style="color:#595D67">]</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 注册 token 解码 UDF，将 INTEGER[] 转换为 VARCHAR。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">attach_function</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    decode_whisper_tokens</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    alias</span><span class="token operator" style="color:#595D67">=</span><span class="token string" style="color:#3F8A3C">"decode_whisper_tokens"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    connection</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">con</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    parameters</span><span class="token operator" style="color:#595D67">=</span><span class="token punctuation" style="color:#595D67">[</span><span class="token plain">TOKEN_IDS_TYPE</span><span class="token punctuation" style="color:#595D67">]</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 读取包含 audio STRUCT 和样本元数据的 Parquet Relation。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">source </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> con</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sql</span><span class="token punctuation" style="color:#595D67">(</span><span class="token triple-quoted-string string" style="color:#3F8A3C">"""</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    SELECT *</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    FROM read_parquet('/data/chinese-speech/*.parquet')</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">"""</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 通过分阶段的 SQL CTE 和最终投影串联已注册的 UDF。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">result </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> source</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">query</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token string" style="color:#3F8A3C">"source"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token triple-quoted-string string" style="color:#3F8A3C">"""</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    WITH audio AS (</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        SELECT</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            * EXCLUDE (audio),</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            -- 从 audio STRUCT 中提取原始音频 BLOB。</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            audio.bytes AS audio_bytes</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        FROM source</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    ),</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    resampled AS (</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        SELECT</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            * EXCLUDE (audio_bytes),</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            -- 阶段 1：调用 Python UDF，将音频解码并重采样为 16 kHz。</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            decode_resample_16k(audio_bytes) AS waveform</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        FROM audio</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    ),</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    featured AS (</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        SELECT</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            * EXCLUDE (waveform),</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            -- 阶段 2：调用 Python UDF，生成 Whisper 输入张量。</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            prepare_whisper_features(waveform) AS input_features</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        FROM resampled</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    ),</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    tokens AS (</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        SELECT</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            * EXCLUDE (input_features),</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            -- 阶段 3：调用 Actor UDF，在 GPU 上执行模型推理。</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            whisper_transcribe_zh(input_features) AS token_ids</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        FROM featured</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    )</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    SELECT</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        * EXCLUDE (token_ids),</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        -- 阶段 4：调用 Python UDF，将 token ID 解码为最终文本。</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        decode_whisper_tokens(token_ids) AS transcription</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    FROM tokens</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    """</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 只写出业务字段和转写文本；写出操作会触发整条 UDF 链执行。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">result</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">write_parquet</span><span class="token punctuation" style="color:#595D67">(</span><span class="token string" style="color:#3F8A3C">"/tmp/chinese_audio_transcriptions.parquet"</span><span class="token punctuation" style="color:#595D67">)</span></span></pre></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq ds" id="视频逐帧检测并提取目标">视频：逐帧检测并提取目标</h2>
<p><strong>典型流程：</strong><span class="link">视频文件 → 帧 Relation → 每帧检测结果 → 目标级 Relation → 裁剪图片</span></p>
<p>视频处理涉及自定义数据源、连续帧张量、GPU Actor 配置和批量目标裁剪，这些步骤使用 Python API 表达更加直接，因此这一节采用 Python First。</p>
<p>视频处理包含两次粒度变化：<span class="link">VideoFrameSource</span> 先把一个视频沿时间轴展开成多帧，目标检测再把一帧中的多个目标展开成多条记录。<span class="link">source_id</span>、<span class="link">video_path</span> 和 <span class="link">frame_index</span> 始终随记录保留，因此检测结果既可以按目标字段查询，也可以回到来源视频。</p>
<p><span class="link">Detector</span> 由 Actor 复用 YOLO 模型；<span class="link">crop_object_batch</span> 展开检测列表，并按边界框裁出 PNG。</p>
<div class="term"><div class="term-bar"><span class="sq sq1"></span><span class="sq sq2"></span><span class="sq sq3"></span><span class="fn">example.py</span><button type="button" class="term-copy" aria-label="复制代码">复制</button></div><pre class="code"><span class="token-line" style="color:#15171E"><span class="token keyword" style="color:#8E3DA8;font-weight:bold">import</span><span class="token plain"> vane</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">from</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">datasource </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">import</span><span class="token plain"> read_datasource</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">from</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">datasource</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">video_reader </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">import</span><span class="token plain"> VideoFrameSource</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 有状态批量回调：传给 map_batches 后，由 Vane 创建并管理 Actor。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">class</span><span class="token plain"> </span><span class="token class-name" style="color:#1A7E7B">Detector</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">def</span><span class="token plain"> </span><span class="token function" style="color:#2E66C4">__init__</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">self</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 每个 Actor 在生命周期内只加载一次 YOLO 模型。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        self</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">model </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> load_yolo</span><span class="token punctuation" style="color:#595D67">(</span><span class="token string" style="color:#3F8A3C">"yolo11n.pt"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> device</span><span class="token operator" style="color:#595D67">=</span><span class="token string" style="color:#3F8A3C">"cuda"</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">def</span><span class="token plain"> </span><span class="token function" style="color:#2E66C4">__call__</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">self</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> table</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token comment" style="color:#9A9DA6;font-style:italic"># __call__ 接收一个 Arrow Table 批次，对其中所有视频帧执行目标检测。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">return</span><span class="token plain"> run_object_detection</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            table</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            model</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">self</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">model</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            frame_column</span><span class="token operator" style="color:#595D67">=</span><span class="token string" style="color:#3F8A3C">"frame"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            fields</span><span class="token operator" style="color:#595D67">=</span><span class="token punctuation" style="color:#595D67">{</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">                </span><span class="token string" style="color:#3F8A3C">"label"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> </span><span class="token string" style="color:#3F8A3C">"boxes.cls"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">                </span><span class="token string" style="color:#3F8A3C">"confidence"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> </span><span class="token string" style="color:#3F8A3C">"boxes.conf"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">                </span><span class="token string" style="color:#3F8A3C">"bbox"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> </span><span class="token string" style="color:#3F8A3C">"boxes.xyxy"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            </span><span class="token punctuation" style="color:#595D67">}</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            </span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 保留来源字段和帧序号，方便定位检测结果。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            pass_through</span><span class="token operator" style="color:#595D67">=</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">                </span><span class="token string" style="color:#3F8A3C">"source_id"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">                </span><span class="token string" style="color:#3F8A3C">"video_path"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">                </span><span class="token string" style="color:#3F8A3C">"frame_index"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">                </span><span class="token string" style="color:#3F8A3C">"frame"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            </span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 无状态批量回调：将每帧的目标列表展开为多条裁剪结果。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">def</span><span class="token plain"> </span><span class="token function" style="color:#2E66C4">crop_object_batch</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">table</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 一帧可能包含多个目标，因此需要展开为多行，并分别裁剪。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">return</span><span class="token plain"> crop_detected_objects</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        table</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        frame_column</span><span class="token operator" style="color:#595D67">=</span><span class="token string" style="color:#3F8A3C">"frame"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        features_column</span><span class="token operator" style="color:#595D67">=</span><span class="token string" style="color:#3F8A3C">"features"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        pass_through</span><span class="token operator" style="color:#595D67">=</span><span class="token punctuation" style="color:#595D67">(</span><span class="token string" style="color:#3F8A3C">"source_id"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> </span><span class="token string" style="color:#3F8A3C">"video_path"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> </span><span class="token string" style="color:#3F8A3C">"frame_index"</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        object_column</span><span class="token operator" style="color:#595D67">=</span><span class="token string" style="color:#3F8A3C">"object"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">con </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">connect</span><span class="token punctuation" style="color:#595D67">(</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># VideoFrameSource 负责视频解码，read_datasource 将输出组织成帧 Relation。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 每行表示一帧，并携带来源、路径、帧序号和帧张量。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">frames </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> read_datasource</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    VideoFrameSource</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        video_paths</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        height</span><span class="token operator" style="color:#595D67">=</span><span class="token number" style="color:#A86420">640</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        width</span><span class="token operator" style="color:#595D67">=</span><span class="token number" style="color:#A86420">640</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    con</span><span class="token operator" style="color:#595D67">=</span><span class="token plain">con</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 第一次 map_batches 调用 Detector；Vane 创建 GPU Actor 并复用其中的模型。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">detected </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> frames</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">map_batches</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    Detector</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token comment" style="color:#9A9DA6;font-style:italic"># schema 描述检测阶段生成的 Relation 中的全部字段。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    schema</span><span class="token operator" style="color:#595D67">=</span><span class="token punctuation" style="color:#595D67">{</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token string" style="color:#3F8A3C">"source_id"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sqltypes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">VARCHAR</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token string" style="color:#3F8A3C">"video_path"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sqltypes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">VARCHAR</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token string" style="color:#3F8A3C">"frame_index"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sqltypes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">BIGINT</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token string" style="color:#3F8A3C">"frame"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> FRAME_TYPE</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token string" style="color:#3F8A3C">"features"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> FEATURE_LIST_TYPE</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token punctuation" style="color:#595D67">}</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    batch_size</span><span class="token operator" style="color:#595D67">=</span><span class="token number" style="color:#A86420">16</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    actor_number</span><span class="token operator" style="color:#595D67">=</span><span class="token number" style="color:#A86420">1</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    gpus</span><span class="token operator" style="color:#595D67">=</span><span class="token number" style="color:#A86420">1.0</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 第二次 map_batches 在 CPU 上调用无状态函数，展开并裁剪目标。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">objects </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> detected</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">map_batches</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    crop_object_batch</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 展开后每行表示一个目标，并增加裁剪后的 PNG BLOB。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    schema</span><span class="token operator" style="color:#595D67">=</span><span class="token punctuation" style="color:#595D67">{</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token string" style="color:#3F8A3C">"source_id"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sqltypes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">VARCHAR</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token string" style="color:#3F8A3C">"video_path"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sqltypes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">VARCHAR</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token string" style="color:#3F8A3C">"frame_index"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sqltypes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">BIGINT</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token string" style="color:#3F8A3C">"features"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> FEATURE_TYPE</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token string" style="color:#3F8A3C">"object"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sqltypes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">BLOB</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token punctuation" style="color:#595D67">}</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># 显式选择最终输出字段；裁剪阶段已经移除帧张量。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">result </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> objects</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">project</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token string" style="color:#3F8A3C">"source_id, video_path, frame_index, features, object"</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># write_parquet 触发前面的读取、检测和裁剪阶段执行。</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">result</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">write_parquet</span><span class="token punctuation" style="color:#595D67">(</span><span class="token string" style="color:#3F8A3C">"/tmp/video_objects.parquet"</span><span class="token punctuation" style="color:#595D67">)</span></span></pre></div>
<p>输出的每一行对应一个检测目标：</p>
<table class="dt"><thead><tr><th>字段</th><th>用途</th></tr></thead><tbody><tr><td><span class="link">source_id</span>、<span class="link">video_path</span></td><td>标识目标来自哪个视频</td></tr><tr><td><span class="link">frame_index</span></td><td>定位目标所在的解码帧</td></tr><tr><td><span class="link">features.label</span></td><td>模型输出的数值类别</td></tr><tr><td><span class="link">features.confidence</span></td><td>目标检测置信度</td></tr><tr><td><span class="link">features.bbox</span></td><td>检测模型输入帧坐标系中的边界框</td></tr><tr><td><span class="link">object</span></td><td>按 bbox 裁出的 PNG BLOB</td></tr></tbody></table>
<p><span class="link">features.label</span> 可按 YOLO 的 <span class="link">names</span> 映射为类别名。<span class="link">bbox</span> 基于 640×640 的模型输入帧；映射回原视频时，还需保留原始宽高和缩放参数。<span class="link">frame_index</span> 只是解码帧序号，若要精确定位播放时间，还需记录时间戳，或 PTS 与 time base。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq ds" id="结束语">结束语</h2>
<p>摘要、转写和检测结果要继续用于查询、复核和写出，就不能和原始文件脱节。文件名、页码、帧号及业务字段随结果保留，查到某条记录时，仍能知道它来自哪里。Vane Data 用 Relation 维持这种对应关系，解析和推理继续交给现有的 Python 工具与模型。以后更换模型或加入业务规则，下游拿到的依然是 schema 清晰、来源明确的数据，无需重新拼接散落在脚本和中间文件里的结果。多模态文件由此进入日常的数据处理，不再停留在一次模型调用里。</p>
<p>开始使用：</p>
<ul class="dl">
<li class=""><a class="dlink" href="https://github.com/AstroVela/demo-scene/blob/main/claims-disposition/README.zh-CN.md">端到端构建多模态理赔审计</a></li>
<li class=""><a class="dlink" href="https://github.com/AstroVela/vane/blob/main/multimodal_inference_benchmarks/document_embedding/vane_main.py">PDF 文档 Embedding 示例</a></li>
<li class=""><a class="dlink" href="https://github.com/AstroVela/vane/blob/main/examples/basic_prompt.py">图片结构化 Prompt 示例</a></li>
<li class=""><a class="dlink" href="https://github.com/AstroVela/vane/blob/main/multimodal_inference_benchmarks/audio_transcription/vane_main.py">音频转写示例</a></li>
<li class=""><a class="dlink" href="https://github.com/AstroVela/vane/blob/main/multimodal_inference_benchmarks/video_object_detection/vane_main.py">视频目标检测示例</a></li>
<li class=""><a class="dlink" href="https://github.com/AstroVela/vane">Vane Data GitHub 仓库</a></li>
<li class=""><a class="dlink" href="https://vane.astrovela.ai/zh-CN/docs/data/quickstart/quickstart">Vane Data 快速开始</a></li>
</ul>]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Vane Data：如何让 DuckDB 成为 AI 多模态数据引擎]]></title>
        <id>https://vane.astrovela.ai/zh-CN/blog/ai-workloads-need-a-new-data-engine</id>
        <link href="https://vane.astrovela.ai/zh-CN/blog/ai-workloads-need-a-new-data-engine"/>
        <updated>2026-08-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[为什么多模态 AI 工作负载需要一个协同关系计算、模型推理与异构资源的数据引擎。]]></summary>
        <content type="html"><![CDATA[<p class="blog-body-lead">Vane Data 是基于 DuckDB 构建的多模态原生数据引擎，将数据处理、模型推理和异构资源调度纳入同一条 Relation 流水线。本文介绍它如何组合 AI Functions、Python UDF 与 vLLM，如何通过动态批处理、流水线并行、背压和容错让任务稳定运行，并以车险理赔为例展示从图片预处理到审核分流的完整流程。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq ds" id="ai-工作负载需要新的数据引擎">AI 工作负载需要新的数据引擎</h2>
<p>AI 工作负载正在从“查表”走向“理解和行动”。AI Agent 的检索、判断和执行依赖多种输入，包括文本、图片、音频、视频和文档。这些多模态数据需要先经过解析、筛选和组织，转化为可查询的结构化形式，才能稳定支撑 Agent 工作流。</p>
<ul class="dl">
<li class=""><strong>传统数据栈难以承载多模态工作流</strong></li>
</ul>
<p>多模态文件对象大小差异很大，单行可能从几字节到数百 MB；模型推理又常常需要 CPU、GPU、网络和存储，它们的吞吐并不一致。传统数据栈通常把数仓、Python 脚本、对象存储和模型服务拼在一起：表引擎负责结构化数据，脚本负责媒体预处理，另一个系统负责推理。数据在系统之间来回搬运，批大小、并发、重试和内存上限只能分别配置，流水线容易出现排队、空转或内存峰值。</p>
<ul class="dl">
<li class=""><strong>Vane Data：把多模态处理带回数据引擎</strong></li>
</ul>
<p>Vane Data 是基于 DuckDB 构建的多模态原生数据引擎，它延续 DuckDB 的 Relation API，把文本、图片和音频等数据作为列，将 AI Functions 与 Python UDF 组合进同一条关系查询，并由 vLLM 等 Provider 执行模型推理。处理后的结果仍可继续筛选、连接、聚合和写出。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq ds" id="从-duckdb-出发构建-ai-数据引擎">从 DuckDB 出发，构建 AI 数据引擎</h2>
<p><a class="dlink" href="https://duckdb.org/">DuckDB</a> 是一款面向分析场景的嵌入式分析数据库，能够直接在应用或 Python 进程中查询本地文件、内存数据和对象存储中的数据，常用于数据探索、ETL、Notebook 与轻量分析，也因此在开发者社区中得到广泛采用。</p>
<ul class="dl">
<li class=""><strong>轻量嵌入：从一个进程开始。</strong> DuckDB 以进程内、无服务依赖的形态运行，通过一条安装命令，即可在本地进行部署，开始分析；对本地文件和内存数据尤其顺手，适合把数据处理嵌入应用、Notebook 与任务脚本。可以说是 OLAP 的 SQLite。</li>
<li class=""><strong>极致性能：为分析负载而生。</strong> DuckDB 面向分析负载融合列式存储、向量化执行、SIMD 指令级并行与多线程调度等技术，形成高效数据流水线，单机即可实现每秒数亿行的分析吞吐。公开的 <a class="dlink" href="https://duckdb.org/2025/10/09/benchmark-results-14-lts">ClickBench 结果</a> 表明了其优异的分析性能。</li>
<li class=""><strong>丰富的生态：按需加载可扩展。</strong> DuckDB 提供了灵活的 <a class="dlink" href="https://duckdb.org/docs/stable/extensions/overview">扩展机制</a>，使得用户可以定义新的数据类型、函数、文件格式以及新的 SQL 语法。数据处理的过程中，经常会涉及到多种数据源。DuckDB 可以查询 Parquet、Iceberg、CSV、JSON 和 Arrow 等，读取 Pandas、Polars 等 Python 对象，并通过该扩展机制连接对象存储、湖仓格式和外部数据库。业务记录、文件和内存中的表可以在同一个查询里组合，少一些中间文件和格式转换。</li>
<li class=""><strong>SQL 与 Python：表达能力强。</strong> 对使用者来说，SQL 和 Python SDK 都可以作为入口。可以用 SQL 写筛选、关联和聚合，也可以通过 Python 的 Relation API 逐步组合查询。</li>
</ul>
<p>因此我们认为，DuckDB 在单机分析领域性能卓越，其设计理念令人称道。基于它构建 AI 多模态原生数据引擎，具备得天独厚的先天优势。于是在 DuckDB 的基础上，构建了 AI 多模态原生数据引擎 Vane Data，旨在帮助用户轻松打造多模态 AI 流水线。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq ds" id="vane-data-多模态原生引擎让数据模型与计算资源协同工作">Vane Data 多模态原生引擎：让数据、模型与计算资源协同工作</h2>
<img class="dimg" style="width:100%;height:auto" src="https://vane.astrovela.ai/img/blog/ai-workloads-need-a-new-data-engine/vane-data-architecture-zh-cn.png" alt="Vane Data 多模态原生数据引擎架构" width="1873" height="840" loading="lazy" decoding="async">
<p>在 DuckDB 的基础上，Vane Data 将多模态算子、AI 调用和资源调度接入同一条基于 Relation 的多模态数据处理流水线，DuckDB 中熟悉的读数据、筛选、聚合与导出逻辑在 Vane Data 中可以继续使用。下面先介绍如何在 Relation 中组织 Prompt、Embedding、UDF 与 vLLM，再说明这些阶段如何在单机与分布式环境中协调 CPU、GPU 和 I/O。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq dss" id="ai-functionsudf-与-vllm基于-relation-构建多模态数据流水线">AI Functions、UDF 与 vLLM：基于 Relation 构建多模态数据流水线</h3>
<p>多模态处理的关键，是让图片或音频等多模态数据和文本、表格字段一样成为 Relation 中的列，沿着可组合、可延迟执行的执行计划流动。Vane Data 可以构建基于 Relation 的多模态数据处理管道，具体特性包括：</p>
<ul class="dl">
<li class=""><strong>AI Functions：让模型调用像列计算一样自然。</strong> Vane Data 支持 <span class="link">ai_prompt</span> 和 <span class="link">ai_embed</span>，既能在 SQL 中调用，也能通过 Python API 使用。<span class="link">ai_prompt</span> 可接收文本或图片，支持 <span class="link">STRUCT</span> 结构化输出；<span class="link">ai_embed</span> 把文本转换为固定维度的向量。它们的结果仍是可过滤、连接、聚合的列；支持按需配置 <span class="link">on_error</span>、重试以及其他 Provider 选项进行更多控制。</li>
<li class=""><strong>Python UDF：让自定义逻辑进入执行计划。</strong> <span class="link">@vane.func</span> 可以将无状态的处理逻辑定义为可在 SQL 中直接调用的函数，<span class="link">@vane.cls</span> 将有状态的处理逻辑定义为函数，它可以在 Actor 内复用模型、客户端或解码器等资源。同时，这两种函数也都提供 batch 模式来批量处理，从而提升性能。</li>
<li class=""><strong>vLLM Provider：使用前缀路由复用 KV Cache。</strong> Vane 的原生 vLLM Provider 在推理之外增加了面向数据流的前缀感知路由。运行时把共享前缀的请求放入有界桶，并尽量送到同一 vLLM Actor，以复用 KV/prefix cache；当该 Actor 的在途负载明显高于其他 Actor 时再做负载均衡。这样既提高重复 Prompt 的缓存命中机会，也避免为了追求亲和性而让单个 Actor 堵塞。</li>
</ul>
<p>在这条链路中，Prompt 调用、Embedding 生成和结果过滤可以通过 Relation API 连续组合，并在最终取数或写出时作为一条完整的数据处理流程执行。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq dss" id="从单机到分布式统一调度-cpugpu-与-io">从单机到分布式：统一调度 CPU、GPU 与 I/O</h3>
<p>前面的能力解决了“如何把多模态操作写进数据流程”，但要让这条流程稳定跑完，还需要回答“在哪里运行、如何分配资源、遇到波动怎么办”。Vane Data 将这些问题交给统一运行时处理，从单机开发到分布式执行，围绕资源、吞吐和可靠性提供以下机制：</p>
<ul class="dl">
<li class=""><strong>两个 Runner，一套业务逻辑。</strong> 创建连接前使用 <span class="link">vane.configure(runner="local")</span> 选择单机 Local Runner，适合开发和小规模任务；改为 <span class="link">runner="ray"</span> 即可交给 Ray Runner 调度多进程或多节点 CPU/GPU。Relation 和业务逻辑保持一致，变化集中在运行时配置与资源声明。</li>
<li class=""><strong>动态批处理：适应数据大小与计算成本。</strong> 运行时同时参考行数和字节数，不把大小悬殊的多模数据对象硬塞进固定行数；超大输入会被拆分，输出缓冲区达到行数或字节阈值就刷新，分区分配器还会根据已观察到的 split 大小逐步调整目标。这样能在模型吞吐、内存峰值和单任务开销之间取得更稳的平衡。</li>
<li class=""><strong>流水线并行：让异构资源同时工作。</strong> 读取/解码、CPU 预处理、GPU 推理和 I/O 写出是不同阶段，各自声明资源、并发和批大小。异步执行图允许相邻阶段重叠：GPU 处理当前批时，CPU 准备下一批，上一批结果同时写出，从而减少某一种资源等待另一种资源的时间。</li>
<li class=""><strong>背压：用有界队列换取稳定吞吐。</strong> 下游 GPU、模型服务或存储变慢时，运行时用有界的在途任务、输出窗口和资源准入限制上游继续提交，并跟踪排队字节与待处理任务。队列不会无限增长，内存和对象存储峰值更可控。</li>
<li class=""><strong>容错：让失败可重试、可隔离、可恢复。</strong> 瞬时 Provider 错误、任务或 Worker 故障可以按策略重试，Actor 也能在重建后重新加载模型或客户端；AI Function 调用可用 <span class="link">on_error="ignore"</span> 转为 <span class="link">NULL</span> 并保留错误信息，或用 <span class="link">raise</span> 让任务明确失败。</li>
</ul>
<p>这些机制共同把一次模型调用扩展成可持续运行的数据流水线：上游准备数据，中间阶段完成推理，下游汇总结果，资源和错误在各阶段之间被显式管理。Vane Data 的价值在于让这套能力仍然可以用熟悉的 DuckDB 方式开始。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq ds" id="案例一条-sql-串起图片预处理ai-识别与审核分流">案例：一条 SQL 串起图片预处理、AI 识别与审核分流</h2>
<p>以车险理赔为例，理赔记录和现场照片分别保存在业务表和图片表中。我们需要先筛选待处理案件并关联照片，再用 Python UDF 完成图片方向校正、尺寸检查和格式统一，随后调用 AI Function 输出结构化的损伤判断，最后结合赔付金额与业务规则决定后续流程。除 UDF 的定义和注册外，整个数据处理过程都由一条 SQL 表达。</p>
<p>结构化输出的 JSON Schema 直接写在示例代码中，将模型返回约束为受损部位、严重程度和置信度三个字段；后续 SQL 可以直接读取这些结构化结果。</p>
<div class="term"><div class="term-bar"><span class="sq sq1"></span><span class="sq sq2"></span><span class="sq sq3"></span><span class="fn">example.py</span><button type="button" class="term-copy" aria-label="复制代码">复制</button></div><pre class="code"><span class="token-line" style="color:#15171E"><span class="token keyword" style="color:#8E3DA8;font-weight:bold">from</span><span class="token plain"> io </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">import</span><span class="token plain"> BytesIO</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">from</span><span class="token plain"> PIL </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">import</span><span class="token plain"> Image</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> ImageOps</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">import</span><span class="token plain"> vane</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># Constrain model output to directly queryable structured fields.</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">damage_schema </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> </span><span class="token triple-quoted-string string" style="color:#3F8A3C">"""{</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">  "type": "object",</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">  "properties": {</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    "damage_part": {"type": "string"},</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    "severity": {"type": "string", "enum": ["low", "medium", "high"]},</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    "confidence": {"type": "number"}</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">  },</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">  "required": ["damage_part", "severity", "confidence"],</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">  "additionalProperties": false</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">}"""</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># Correct orientation, validate dimensions, and normalize the image to JPEG.</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:#595D67">@vane</span><span class="token decorator annotation punctuation" style="color:#595D67">.</span><span class="token decorator annotation punctuation" style="color:#595D67">func</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">return_dtype</span><span class="token operator" style="color:#595D67">=</span><span class="token string" style="color:#3F8A3C">"BLOB"</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">def</span><span class="token plain"> </span><span class="token function" style="color:#2E66C4">prepare_image</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">raw</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> </span><span class="token builtin" style="color:#1A7E7B">bytes</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"> </span><span class="token operator" style="color:#595D67">-</span><span class="token operator" style="color:#595D67">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:#1A7E7B">bytes</span><span class="token plain"> </span><span class="token operator" style="color:#595D67">|</span><span class="token plain"> </span><span class="token boolean" style="color:#8E3DA8;font-weight:bold">None</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">try</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">with</span><span class="token plain"> Image</span><span class="token punctuation" style="color:#595D67">.</span><span class="token builtin" style="color:#1A7E7B">open</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">BytesIO</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">raw</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"> </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">as</span><span class="token plain"> image</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            image </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> ImageOps</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">exif_transpose</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">image</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">convert</span><span class="token punctuation" style="color:#595D67">(</span><span class="token string" style="color:#3F8A3C">"RGB"</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">if</span><span class="token plain"> </span><span class="token builtin" style="color:#1A7E7B">min</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">image</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">size</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"> </span><span class="token operator" style="color:#595D67">&lt;</span><span class="token plain"> </span><span class="token number" style="color:#A86420">480</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">                </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">return</span><span class="token plain"> </span><span class="token boolean" style="color:#8E3DA8;font-weight:bold">None</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            image</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">thumbnail</span><span class="token punctuation" style="color:#595D67">(</span><span class="token punctuation" style="color:#595D67">(</span><span class="token number" style="color:#A86420">1024</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> </span><span class="token number" style="color:#A86420">1024</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            output </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> BytesIO</span><span class="token punctuation" style="color:#595D67">(</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            image</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">save</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">output</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> </span><span class="token string" style="color:#3F8A3C">"JPEG"</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> quality</span><span class="token operator" style="color:#595D67">=</span><span class="token number" style="color:#A86420">85</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">            </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">return</span><span class="token plain"> output</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">getvalue</span><span class="token punctuation" style="color:#595D67">(</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">except</span><span class="token plain"> </span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain">OSError</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"> TypeError</span><span class="token punctuation" style="color:#595D67">)</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">        </span><span class="token keyword" style="color:#8E3DA8;font-weight:bold">return</span><span class="token plain"> </span><span class="token boolean" style="color:#8E3DA8;font-weight:bold">None</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">connection </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> vane</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">connect</span><span class="token punctuation" style="color:#595D67">(</span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># Compose relational processing, the Python UDF, the AI Function, and business rules.</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">routes </span><span class="token operator" style="color:#595D67">=</span><span class="token plain"> connection</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">sql</span><span class="token punctuation" style="color:#595D67">(</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    </span><span class="token triple-quoted-string string" style="color:#3F8A3C">"""</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    WITH prepared AS (</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        -- Join, filter, and preprocess each image with the Python UDF.</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        SELECT c.claim_id, c.claim_amount,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">               prepare_image(i.content) AS image</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        FROM pending_claims AS c</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        LEFT JOIN claim_images AS i USING (claim_id)</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        WHERE c.status = 'pending'</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    )</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    SELECT</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        claim_id,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        assessment.*,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        -- Combine the model result and claim amount for business routing.</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        CASE</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            WHEN image IS NULL THEN 'Request more photos'</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            WHEN assessment IS NULL OR assessment.confidence &lt; 0.65 THEN 'Manual review'</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            WHEN assessment.severity = 'high' OR claim_amount &gt;= 50000 THEN 'Claims review'</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">            ELSE 'Automatic routing'</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        END AS next_step</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    FROM (</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        SELECT *,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">               -- Convert the preprocessed image into a structured assessment.</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">               CASE WHEN image IS NOT NULL THEN ai_prompt(</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                   'Identify the damaged vehicle part and severity.',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                   image,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                   return_format := $damage_schema,</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                   system_message := 'Use only visual evidence; lower confidence when uncertain.',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                   provider := 'openai',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                   model := 'gpt-4o-mini',</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">                   on_error := 'ignore'</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">               ) END AS assessment</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">        FROM prepared</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    ) AS assessed</span>
</span><span class="token-line" style="color:#15171E"><span class="token triple-quoted-string string" style="color:#3F8A3C">    """</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">    params</span><span class="token operator" style="color:#595D67">=</span><span class="token punctuation" style="color:#595D67">{</span><span class="token string" style="color:#3F8A3C">"damage_schema"</span><span class="token punctuation" style="color:#595D67">:</span><span class="token plain"> damage_schema</span><span class="token punctuation" style="color:#595D67">}</span><span class="token punctuation" style="color:#595D67">,</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token punctuation" style="color:#595D67">)</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain" style="display:inline-block">
</span>
</span><span class="token-line" style="color:#15171E"><span class="token plain"></span><span class="token comment" style="color:#9A9DA6;font-style:italic"># Materialize the complete processing plan.</span><span class="token plain"></span>
</span><span class="token-line" style="color:#15171E"><span class="token plain">routes</span><span class="token punctuation" style="color:#595D67">.</span><span class="token plain">write_parquet</span><span class="token punctuation" style="color:#595D67">(</span><span class="token string" style="color:#3F8A3C">"claim_routes.parquet"</span><span class="token punctuation" style="color:#595D67">)</span></span></pre></div>
<p>在这个流程中，Python 负责图片解码和预处理，SQL 负责关联、筛选与业务分流，<span class="link">ai_prompt</span> 则把图片转换为结构化判断。AI 返回的结果是可以继续参与条件判断和后续写出的列。最终调用 <span class="link">write_parquet</span> 时，Vane Data 才执行这条完整的数据处理计划。这个案例展示了 Vane 如何将关系计算、Python 逻辑与 AI 推理放在同一个数据流水线中。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq ds" id="从-duckdb-走向多模态-ai-数据处理">从 DuckDB 走向多模态 AI 数据处理</h2>
<p>Vane Data 让熟悉 DuckDB 的开发者以更低学习成本进入多模态、智能化数据处理时代：用 SQL/Python 表达关系，用 AI Functions/UDF 处理多模数据，再由统一运行时协调 CPU、GPU 与分布式资源。</p>
<p>开始探索：</p>
<ul class="dl">
<li class=""><a class="dlink" href="https://github.com/AstroVela/vane">GitHub 仓库</a></li>
<li class=""><a class="dlink" href="https://vane.astrovela.ai/zh-CN/docs/data/quickstart/quickstart">快速开始</a></li>
<li class=""><a class="dlink" href="https://github.com/AstroVela/demo-scene/tree/main/claims-disposition">完整可直接运行的 Demo：可审计的多模态理赔分流</a></li>
<li class=""><a class="dlink" href="https://vane.astrovela.ai/zh-CN/benchmarks">Benchmark</a></li>
</ul>]]></content>
    </entry>
</feed>