Snowflake's AI_PARSE_DOCUMENT got an agentic mode

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

Share
Snowflake's AI_PARSE_DOCUMENT got an agentic mode

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?

Parsing documents with AI_PARSE_DOCUMENT | Snowflake Documentation

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 or DELETED.
  3. NEW and MODIFIED docs get fed to AI_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.
0:00
/0:06

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 implies page_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.

FilePagesOCR charsAGENTIC chars
PDF, insurance statement3439,17443,677
PDF, invoice32,1712,522
PDF, single page12,0412,197
PPTX, 28 slides2810,56610,842
PPTX, 23 slides2312,58612,125
PPTX, 6 slides65,6745,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 follows image_type: Photos carry description and visible_text; an app screenshot carries app_or_page, purpose, state_signals and visible_text; charts carry chart_type and key_values; diagrams carry a components 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_typeImagesFilesContent
other198Swiss QR-bill codes, one grainy photo
photo_with_text114signature blocks
chart, diagram, table, infographic00none

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:

ModePagesCreditsPer 1,000 pages
OCR950.06460.68
AGENTIC95 (same six files)0.77718.18
AGENTIC528 (20-file run)4.3198.18
AGENTIC64 (three separate runs)0.52358.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:

BeforeAfter
PPTX files in the pages table115115
PPTX pages1,5591,559
NULL pages00
Pages with appended chart/diagram/table text0380
Chunks3,8774,274
Chunks containing image text01,110
Non-PPTX page rows5,9685,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 😎