{"slug": "snowflake-s-ai-parse-document-got-an-agentic-mode", "title": "Snowflake's AI_PARSE_DOCUMENT got an agentic mode", "summary": "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.", "body_md": "[Lakehouse](https://www.sfrt.io/tag/lakehouse/)\n\n# Snowflake's AI_PARSE_DOCUMENT got an agentic mode\n\n12x the price of OCR per page, and it earned that on one file type\n\nTL/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 😅 \n\nI 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.\n\n## The question\n\nThe new mode was released (to public preview) on October 28th, 2026. Would it help me with any of the document pipelines already running?\n\nI 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...\n\n## What the pipeline looks like\n\nI 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.\n\nAnd this happens downstream:\n\n1. An external stage sits on top of the blob container.\n2. 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` .\n3. `NEW` and`MODIFIED` docs get fed to`AI_PARSE_DOCUMENT` with page split enabled, and land in a pages table.\n4. 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.\n5. And finally, a Cortex Search service on top of those chunks is what the agent queries.\n\nWhat the stage held:\n\n- 18,352 .pdf, 64 GB\n- 6,459 .docx, 1.3 GB\n- 800 .aspx, 3 MB\n- 115 .pptx, 1.8 GB\n\nEvery one of those files was previously parsed with `{'mode': 'OCR', 'page_split': true}`.\n\n## What the docs say about agentic mode\n\nThree 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.\n\nOf course, that mode has its own limits:\n\n- Agentic mode caps at 2,000 images per document.\n- `extract_images` doesn't work here... set it to TRUE and the function hands back NULL.\n- 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.\n- The image schemas describe what the model is expected to output, with no guarantee that it conforms. Any field I depend on needs validation.\n- `page_filter` limits the work to selected pages and implies`page_split` . That's the cheapest way to play with the new mode 😜\n\nBilling is quoted per page with no mention of a separate agentic rate. Which meant I had to measure it myself...\n\n## A first comparison on just a few files\n\nI 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.\n\n| File | Pages | OCR chars | AGENTIC chars | \n|---|---|---|---|\n| PDF, insurance statement | 34 | 39,174 | 43,677 | \n| PDF, invoice | 3 | 2,171 | 2,522 | \n| PDF, single page | 1 | 2,041 | 2,197 | \n| PPTX, 28 slides | 28 | 10,566 | 10,842 | \n| PPTX, 23 slides | 23 | 12,586 | 12,125 | \n| PPTX, 6 slides | 6 | 5,674 | 5,917 | \n\nPages 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.\n\nFor all six files the `images` column came back NULL. I initially suspected the sample probably had no visuals in it. That was wrong, though...\n\n## Where the image data actually is\n\nTurn `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:\n\n```\n{\"metadata\": {...},\n \"pages\": [\n   {\"index\": 0, \"content\": \"# Title\\n\\n![img-0.jpeg](img-0.jpeg)\", \"dimensions\": {...},\n    \"images\": [{\"id\": \"img-0.jpeg\", \"top_left_x\": ..., \"results\": {...}}]}\n ]}\n```\n\nTwo notes on how to use this:\n\n- `content` only holds placeholders (`![img-0.jpeg](img-0.jpeg)` ), the summaries are not inlined - so there is no visual interpretation here.\n- 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.\n\n## What kinds of images exist in practice\n\nThe 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`:\n\n| image_type | Images | Files | Typical content | \n|---|---|---|---|\n| other | 39 | 14 | logos, stock illustrations | \n| photo_with_text | 13 | 7 | photos with cropped text | \n| diagram | 14 | 4 | swimlane process flows, quarterly roadmaps, a risk framework drawn as a temple | \n| infographic | 6 | 1 | posters with sections and key facts | \n| chart | 5 | 2 | bar charts | \n| table | 3 | 1 | wide process/control matrices | \n\nThe `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.\n\nHowever, 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.\n\n## Do PDFs have the same payoff?\n\nSince 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.\n\n| image_type | Images | Files | Content | \n|---|---|---|---|\n| other | 19 | 8 | Swiss QR-bill codes, one grainy photo | \n| photo_with_text | 11 | 4 | signature blocks | \n| chart, diagram, table, infographic | 0 | 0 | none | \n\nInvoices, 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.\n\n## What it costs\n\nCredits and a `metrics` array with the page count sit in `SNOWFLAKE.ACCOUNT_USAGE.CORTEX_AI_FUNCTIONS_USAGE_HISTORY`, one row per query and model:\n\n| Mode | Pages | Credits | Per 1,000 pages | \n|---|---|---|---|\n| OCR | 95 | 0.0646 | 0.68 | \n| AGENTIC | 95 (same six files) | 0.7771 | 8.18 | \n| AGENTIC | 528 (20-file run) | 4.319 | 8.18 | \n| AGENTIC | 64 (three separate runs) | 0.5235 | 8.18 | \n\nEvery 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.\n\nProjected 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.\n\n## The pipeline change\n\nWhat 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:\n\n```\ncte_content as (\n    select p.blob_file_path, p.blob_last_modified\n        , ai_parse_document(\n            to_file('@raw.sharepoint.s_documents', p.blob_file_path),\n            {'mode': 'AGENTIC', 'page_split': true}\n          )                                             as parsed_pages\n    from cte_to_process p\n    where p.file_extension ilike '.pptx'\n    union all\n    select p.blob_file_path, p.blob_last_modified\n        , ai_parse_document(\n            to_file('@raw.sharepoint.s_documents', p.blob_file_path),\n            {'mode': 'OCR', 'page_split': true}\n          )                                             as parsed_pages\n    from cte_to_process p\n    where p.file_extension not ilike '.pptx'\n),\ncte_pages as (\n    select c.blob_file_path, c.blob_last_modified\n        , f.value:index::integer        as page_index\n        , f.value:content::varchar      as page_text\n        , f.value:images                as page_images\n    from cte_content c\n        , lateral flatten(input => c.parsed_pages:pages) f\n),\ncte_image_text as (\n    select pg.blob_file_path, pg.page_index\n        , listagg(\n            array_to_string(array_construct_compact(\n                  concat('[', im.value:results:image_type::varchar, ']')\n                , im.value:results:summary::varchar\n                , im.value:results:details:chart_type::varchar\n                , nullif(array_to_string(coalesce(\n                      im.value:results:details:key_values\n                    , im.value:results:key_values), '; '), '')\n                , nullif(array_to_string(transform(\n                      im.value:results:details:components\n                    , c -> c:label::varchar), ', '), '')\n                , nullif(array_to_string(\n                      im.value:results:details:columns, ', '), '')\n              ), ' ')\n            , '\\n')                     as image_text\n    from cte_pages pg\n        , lateral flatten(input => pg.page_images) im\n    where im.value:results:image_type::varchar\n              in ('chart', 'diagram', 'table', 'infographic')\n    group by pg.blob_file_path, pg.page_index\n)\nselect ...\n    , nullif(array_to_string(array_construct_compact(\n          pg.page_text, it.image_text), '\\n\\n'), '')    as page_content\nfrom cte_pages pg\n    join _public_docs pd on pd.blob_file_path = pg.blob_file_path\n    left join cte_image_text it\n        on it.blob_file_path = pg.blob_file_path\n        and it.page_index = pg.page_index\n```\n\nA few side notes on this:\n\n- My first attempt used `concat_ws` and produced empty strings where the image text should have been...`array_construct_compact` handles NULL differently.\n- Empty pages stay NULL thanks to the outer `nullif(..., '')` . The chunker skips NULLs, but a blank string would have created empty chunks.\n- 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.\n\nIn the index, a page containing a chart now looks like this:\n\n```\n<normal slide text>\n\n[chart] A grouped bar chart showing the frequency of five psychological topics ...\nbar \"suicidal thoughts\" is the most frequent topic across all periods, peaking at 4,754 ...\n```\n\n## The run that succeeded and did nothing at all\n\nThe 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 😅\n\nLooking 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.\n\nI investigated the syntax:\n\n- `ai_parse_document(..., {'mode': 'AGENTIC', ...})` with a literal returns the object just fine.\n- the same call with `{'mode': case when p.file_extension ilike '.pptx' then 'AGENTIC' else 'OCR' end, ...}` returns NULL. No error, no warning.\n\nMy 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.\n\n## And the real run\n\nThe same statements again took slightly over 20 minutes this time, against 8 for the empty run. Straight off the tables:\n\n|  | Before | After | \n|---|---|---|\n| PPTX files in the pages table | 115 | 115 | \n| PPTX pages | 1,559 | 1,559 | \n| NULL pages | 0 | 0 | \n| Pages with appended chart/diagram/table text | 0 | 380 | \n| Chunks | 3,877 | 4,274 | \n| Chunks containing image text | 0 | 1,110 | \n| Non-PPTX page rows | 5,968 | 5,968 | \n\nRoughly 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 😎", "url": "https://wpnews.pro/news/snowflake-s-ai-parse-document-got-an-agentic-mode", "canonical_source": "https://www.sfrt.io/snowflakes-ai_parse_document-got-an-agentic-mode/", "published_at": "2026-11-04 06:45:00+00:00", "updated_at": "2026-11-04 06:50:41.753700+00:00", "lang": "en", "topics": ["ai-tools", "ai-products", "artificial-intelligence", "large-language-models"], "entities": ["Snowflake", "AI_PARSE_DOCUMENT", "Cortex Search", "SharePoint", "dlt", "Azure Blob Storage", "Azure Graph", "dltHub"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/snowflake-s-ai-parse-document-got-an-agentic-mode", "markdown": "https://wpnews.pro/news/snowflake-s-ai-parse-document-got-an-agentic-mode.md", "text": "https://wpnews.pro/news/snowflake-s-ai-parse-document-got-an-agentic-mode.txt", "jsonld": "https://wpnews.pro/news/snowflake-s-ai-parse-document-got-an-agentic-mode.jsonld"}}