cd /news/ai-tools/snowflake-s-ai-parse-document-got-an… · home › topics › ai-tools › article
[ARTICLE · art-163654] src=sfrt.io ↗ pub= topic=ai-tools verified=true sentiment=· neutral

Snowflake's AI_PARSE_DOCUMENT got an agentic mode

Snowflake's AI_PARSE_DOCUMENT added an AGENTIC mode, released to public preview on October 28, 2026, which classifies charts, diagrams, tables and infographics in a document and returns semantic summaries alongside extracted text. In a hands-on test over a SharePoint library of 18,352 PDFs (64 GB), 6,459 .docx files (1.3 GB), 800 .aspx files (3 MB) and 115 .pptx files (1.8 GB), the author measured AGENTIC at 8.18 credits per 1,000 pages versus 0.68 for OCR, and found slide decks were the only file type where the 12x cost paid off. The author also reported two undocumented behaviors: image summaries live outside the page content, and the mode parameter must be a literal, since a CASE expression returns NULL with no error.

by read11 min views5 publishedNov 4, 2026
Snowflake's AI_PARSE_DOCUMENT got an agentic mode
Image: Sfrt (auto-discovered)

Lakehouse

12x the price of OCR per page, and it earned that on one file type

TL/DR: Snowflake's AI_PARSE_DOCUMENT now has an AGENTIC mode. It classifies the charts, diagrams, tables and infographics inside a document, writes a summary of each, and hands that back alongside extracted text. I ran it over my SharePoint library that feeds one of my Cortex Search services. Measured cost: 8.18 credits per 1,000 pages (OCR at 0.68). Slide decks were the only place it earned the difference, my PDFs had nothing worth describing 😅

I learned two things not mentioned in the docs: image summaries live outside the page content, and mode has to be a literal. Putting a CASE expression there returns NULL with no error at all.

The question #

The new mode was released (to public preview) on October 28th, 2026. Would it help me with any of the document pipelines already running?

I load many docs from SharePoint into ❄️, parse them there and feed a search service with the content. So that's my obvious playground to test that new mode...

What the pipeline looks like #

I use a dlt pipeline to load the docs into Azure Blob Storage. For the docs themselves I wouldn't really need dlt, but I also load the SharePoint permissions along with the documents (using Azure Graph + SharePoint REST APIs) for obvious reasons (not every user should read every doc). So I bundled those two components into one pipeline running on dltHub.

And this happens downstream:

  1. An external stage sits on top of the blob container.
  2. Every time the dlt pipeline load is done, a task (triggered as a last step at the end of the dlt pipeline) lists the stage directory, MD5s it against a metadata table, and tags each file NEW ,MODIFIED orDELETED .
  3. NEW andMODIFIED docs get fed toAI_PARSE_DOCUMENT with page split enabled, and land in a pages table.
  4. Chunking then cuts pages into 1,400-char pieces with a 200-char overlap (neighbouring pages ride along as context) and writes them to a chunks table.
  5. And finally, a Cortex Search service on top of those chunks is what the agent queries.

What the stage held:

  • 18,352 .pdf, 64 GB
  • 6,459 .docx, 1.3 GB
  • 800 .aspx, 3 MB
  • 115 .pptx, 1.8 GB

Every one of those files was previously parsed with {'mode': 'OCR', 'page_split': true}.

What the docs say about agentic mode #

Three modes are available: LAYOUT (text with structure, tables and headers), OCR (plain fast extraction) and AGENTIC. Using the agentic mode, Snowflake looks at the visual elements in a file (charts, diagrams, screenshots, forms, images), classifies them, decides which processing each of them needs, and returns semantic summaries plus structured data: chart series and their values, table headers and cells, diagram components and how they relate, all next to the usual text and layout.

Of course, that mode has its own limits:

  • Agentic mode caps at 2,000 images per document.
  • extract_images doesn't work here... set it to TRUE and the function hands back NULL.
  • Accepted types are PDF, PPTX, DOCX, JPEG, JPG, PNG, TIFF, TIF, HTML, TXT. A file can be at most 100 MB and 2,000 pages.
  • The image schemas describe what the model is expected to output, with no guarantee that it conforms. Any field I depend on needs validation.
  • page_filter limits the work to selected pages and impliespage_split . That's the cheapest way to play with the new mode 😜

Billing is quoted per page with no mention of a separate agentic rate. Which meant I had to measure it myself...

A first comparison on just a few files #

I randomly picket three PPTX and three PDFs between 1.5 and 10 MB, each parsed twice (OCR and AGENTIC). Then I compared pages, characters and image counts.

File Pages OCR chars AGENTIC chars
PDF, insurance statement 34 39,174 43,677
PDF, invoice 3 2,171 2,522
PDF, single page 1 2,041 2,197
PPTX, 28 slides 28 10,566 10,842
PPTX, 23 slides 23 12,586 12,125
PPTX, 6 slides 6 5,674 5,917

Pages came back equal and characters landed within roughly 10% of each other, so agentic doesn't extract more text. However, the reading order is changed: On a two-column slide, OCR interleaves the columns line by line, whereas agentic gives back clean markdown with headings and | table | rows.

For all six files the images column came back NULL. I initially suspected the sample probably had no visuals in it. That was wrong, though...

Where the image data actually is #

Turn page_split on and the images aren't at the top level of the response anymore. Instead, every page object carries its own images array:

{"metadata": {...},
 "pages": [
   {"index": 0, "content": "# Title\n\n![img-0.jpeg](img-0.jpeg)", "dimensions": {...},
    "images": [{"id": "img-0.jpeg", "top_left_x": ..., "results": {...}}]}
 ]}

Two notes on how to use this:

  • content only holds placeholders (![img-0.jpeg](img-0.jpeg) ), the summaries are not inlined - so there is no visual interpretation here.
  • The shape of results followsimage_type : Photos carrydescription andvisible_text ; an app screenshot carriesapp_or_page ,purpose ,state_signals andvisible_text ; charts carrychart_type andkey_values ; diagrams carry acomponents array of labels and roles.

What kinds of images exist in practice #

The second sample: 8 random PPTX plus 8 random PDFs, first 8 pages of each (page_filter: [{'start': 0, 'end': 8}]). I flattened every image and grouped by image_type:

image_type Images Files Typical content
other 39 14 logos, stock illustrations
photo_with_text 13 7 photos with cropped text
diagram 14 4 swimlane process flows, quarterly roadmaps, a risk framework drawn as a temple
infographic 6 1 posters with sections and key facts
chart 5 2 bar charts
table 3 1 wide process/control matrices

The chart output is most interesting here. One of them returned this: "most frequent topic across all periods, peaking at 4,754 in the fourth period; all categories except one show a continuous upward trend [...]". A roadmap diagram came back with every initiative label and its status. None of that appeared in the OCR text of the same slides.

However, image descriptions are mostly noise 😅 The first four types in the above list (logos and stock photos) would just dump junk into a search index. The filter I ended up with keeps chart, diagram, table and infographic, and throws away other and photo_with_text. But that decision presumably is very dependent on the use case.

Do PDFs have the same payoff? #

Since 18,000 of the files are PDFs, those would be the major cost drivers. Another experimental run: 12 random PDFs sized 1 to 20 MB, first 8 pages, agentic only.

image_type Images Files Content
other 19 8 Swiss QR-bill codes, one grainy photo
photo_with_text 11 4 signature blocks
chart, diagram, table, infographic 0 0 none

Invoices, statements and contracts make up most of my PDF library. Nothing from the sample would have survived the previously mentioned filter. Now, twelve files can easily miss a rare type, and a few chart-heavy reports probably exist somewhere in there (I didn't look at each PDF and its content, obviously), but that i.m.h.o. doesn't justify paying a significantly higher price across every PDF.

What it costs #

Credits and a metrics array with the page count sit in SNOWFLAKE.ACCOUNT_USAGE.CORTEX_AI_FUNCTIONS_USAGE_HISTORY, one row per query and model:

Mode Pages Credits Per 1,000 pages
OCR 95 0.0646 0.68
AGENTIC 95 (same six files) 0.7771 8.18
AGENTIC 528 (20-file run) 4.319 8.18
AGENTIC 64 (three separate runs) 0.5235 8.18

Every agentic query landed on a flat 8.18 credits per 1,000 pages, no matter how many images a page carried. A page of plain text bills the same as a page full of charts. That's roughly 12 times what OCR costs.

Projected onto my SharePoint catalog: 115 public PPTX covering 1,559 pages (the exact page count came straight from the already existing pages table), so about 12.8 credits under agentic - compared to roughly 1.1 for OCR. When I apply the same math on the PDFs, it's a few hundred credits above OCR... for a result that (presumably!) adds value only on rare occasion.

The pipeline change #

What I settled on: AGENTIC for PPTX files, OCR for everything else. And image summaries only for the four useful types appended to the page text. The (non-ASPX) parse step of my task turned into:

cte_content as (
    select p.blob_file_path, p.blob_last_modified
        , ai_parse_document(
            to_file('@raw.sharepoint.s_documents', p.blob_file_path),
            {'mode': 'AGENTIC', 'page_split': true}
          )                                             as parsed_pages
    from cte_to_process p
    where p.file_extension ilike '.pptx'
    union all
    select p.blob_file_path, p.blob_last_modified
        , ai_parse_document(
            to_file('@raw.sharepoint.s_documents', p.blob_file_path),
            {'mode': 'OCR', 'page_split': true}
          )                                             as parsed_pages
    from cte_to_process p
    where p.file_extension not ilike '.pptx'
),
cte_pages as (
    select c.blob_file_path, c.blob_last_modified
        , f.value:index::integer        as page_index
        , f.value:content::varchar      as page_text
        , f.value:images                as page_images
    from cte_content c
        , lateral flatten(input => c.parsed_pages:pages) f
),
cte_image_text as (
    select pg.blob_file_path, pg.page_index
        , listagg(
            array_to_string(array_construct_compact(
                  concat('[', im.value:results:image_type::varchar, ']')
                , im.value:results:summary::varchar
                , im.value:results:details:chart_type::varchar
                , nullif(array_to_string(coalesce(
                      im.value:results:details:key_values
                    , im.value:results:key_values), '; '), '')
                , nullif(array_to_string(transform(
                      im.value:results:details:components
                    , c -> c:label::varchar), ', '), '')
                , nullif(array_to_string(
                      im.value:results:details:columns, ', '), '')
              ), ' ')
            , '\n')                     as image_text
    from cte_pages pg
        , lateral flatten(input => pg.page_images) im
    where im.value:results:image_type::varchar
              in ('chart', 'diagram', 'table', 'infographic')
    group by pg.blob_file_path, pg.page_index
)
select ...
    , nullif(array_to_string(array_construct_compact(
          pg.page_text, it.image_text), '\n\n'), '')    as page_content
from cte_pages pg
    join _public_docs pd on pd.blob_file_path = pg.blob_file_path
    left join cte_image_text it
        on it.blob_file_path = pg.blob_file_path
        and it.page_index = pg.page_index

A few side notes on this:

  • My first attempt used concat_ws and produced empty strings where the image text should have been...array_construct_compact handles NULL differently.
  • Empty pages stay NULL thanks to the outer nullif(..., '') . The chunker skips NULLs, but a blank string would have created empty chunks.
  • Scalar subqueries can't contain a correlated flatten ("Unsupported subquery type cannot be evaluated"), so the image text sits in its own CTE and joins back on its file's page.

In the index, a page containing a chart now looks like this:

<normal slide text>

[chart] A grouped bar chart showing the frequency of five psychological topics ...
bar "suicidal thoughts" is the most frequent topic across all periods, peaking at 4,754 ...

The run that succeeded and did nothing at all #

The first manual run took 488 seconds, then SUCCEEDED. All 115 decks sat in the metadata table marked as processed. t_documents and t_document_chunks held zero PPTX rows, though 😅

Looking into ACCOUNT_USAGE.QUERY_HISTORY on the task's query tag: the insert burned 447 seconds and inserted 0 rows, while the later metadata merge inserted 115 rows. Seven minutes of parsing, nothing to show for it, no error anywhere.

I investigated the syntax:

  • ai_parse_document(..., {'mode': 'AGENTIC', ...}) with a literal returns the object just fine.
  • the same call with {'mode': case when p.file_extension ilike '.pptx' then 'AGENTIC' else 'OCR' end, ...} returns NULL. No error, no warning.

My intention here was to pick the mode per row with a CASE because it looked tidy. But the function wants a constant. Hence the two-branch union all above.

And the real run #

The same statements again took slightly over 20 minutes this time, against 8 for the empty run. Straight off the tables:

Before After
PPTX files in the pages table 115 115
PPTX pages 1,559 1,559
NULL pages 0 0
Pages with appended chart/diagram/table text 0 380
Chunks 3,877 4,274
Chunks containing image text 0 1,110
Non-PPTX page rows 5,968 5,968

Roughly a quarter of the pages carry visual content worth indexing (plus the corrected read order mentioned earlier), the chunks grew about 10%. The task's final step refreshed the Cortex Search service. From now on only new or modified PPTX pay the higher rate, so in steady state the gap to OCR stays small 😎

── more in #ai-tools 4 stories · sorted by recency
── more on @snowflake 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/snowflake-s-ai-parse…] indexed:0 read:11min 2026-11-04 · —