Free lesson · GenAI Data Engineering

બધા extraction outputs ને normalize કરતું એક unified document model ડિઝાઇન કરો

Typed content blocks અને extraction method metadata સાથે format-agnostic document schema બનાવો. Docling, VLM અને Document AI outputs માટે adapters બનાવો.

Course: GenAI Data Pipelines · Chapter 1 · Document Ingestion with VLMs

Free to read — no subscription required.

પરિચય

જ્યારે તમે Docling, Document AI અને vision-language મોડેલમાંથી ડેટા ખેંચતી ઇન્જેશન પાઇપલાઇન બનાવો છો, ત્યારે દરેક બેકએન્ડ પોતાના આકારમાં સામગ્રી પરત કરે છે — અહીં bounding boxes, ત્યાં પેજ હાયરાર્કી, બીજે ફ્રીફોર્મ JSON — અને દરેક ડાઉનસ્ટ્રીમ કન્ઝ્યુમર if extractor == "docling": … પર બ્રાન્ચ કરવા લાગે છે, જ્યાં સુધી કોડબેઝ એક્સ્ટ્રેક્ટરના નામોની આસપાસ જડ બની ન જાય. જે ટીમો એકીકૃત દસ્તાવેજ મોડેલ છોડી દે છે તે આગામી ક્વાર્ટર દર વખતે નવો એક્સ્ટ્રેક્ટર આવે ત્યારે chunkers, embedders અને search indexers ફરીથી લખવામાં વિતાવે છે. અંત સુધીમાં તમે discriminated-union કન્ટેન્ટ બ્લોક્સ, provenance મેટાડેટા અને સ્થિર adapter સીમા સાથે એક canonical દસ્તાવેજ સ્કીમા ડિઝાઇન કરી શકશો, જેથી ડાઉનસ્ટ્રીમ સિસ્ટમો કયા એક્સ્ટ્રેક્ટરે તેને બનાવ્યું તેની પરવા કર્યા વિના એક જ આકારનો ઉપયોગ કરે.

મુખ્ય પરિભાષા

  • એકીકૃત દસ્તાવેજ મોડેલ (Unified document model): એક જ canonical સ્કીમા (અહીં, UnifiedDocument) જેને દરેક એક્સ્ટ્રેક્ટર adapter લક્ષ્ય બનાવે છે, જેથી ડાઉનસ્ટ્રીમ chunkers, embedders અને indexers એક્સ્ટ્રેક્ટરના નામ પર બ્રાન્ચ કરવાને બદલે એક જ આકારનો ઉપયોગ કરે.
  • Discriminated union: એક polymorphic કન્ટેન્ટ-બ્લોક રજૂઆત જ્યાં દરેક variant એક literal block_type ટેગ ધરાવે છે (દા.ત. TableBlock પર Literal["table"]), જેનો ઉપયોગ Pydantic મિશ્રિત બ્લોક્સની યાદીઓને કોઈ અસ્પષ્ટતા વિના deserialize કરવા માટે કરે છે.
  • Provenance મેટાડેટા: પ્રત્યેક બ્લોક માટે કયા એક્સ્ટ્રેક્ટરે તે બ્લોક બનાવ્યો તેનું ટ્રેકિંગ (નામ, વર્ઝન, પેજ, bounding box, confidence, quality_flags), જે બ્લોક પર જ રાખવામાં આવે છે જેથી quality-aware પ્રોસેસિંગ ડાઉનસ્ટ્રીમમાં ફિલ્ટર કરી શકે અથવા ફરીથી રૂટ કરી શકે.
  • Extraction adapter: એક અલગ ક્લાસ (દા.ત. DoclingAdapter) જેનું એકમાત્ર કામ એક એક્સ્ટ્રેક્ટરના native આઉટપુટને UnifiedDocument માં અનુવાદિત કરવાનું છે, જે ઇન્જેશનને એક્સ્ટ્રેક્ટરની પસંદગીથી અલગ રાખતી સ્થિર સીમા બનાવે છે.

ખ્યાલો

આ પાઠ ત્રણ વિચારો શીખવે છે જે એક જ ડિઝાઇન તરીકે એકસાથે બંધબેસે છે.

એક canonical સ્કીમા, ઘણા adapters. UnifiedDocument મોડેલ એ કરાર છે જેના પર દરેક ડાઉનસ્ટ્રીમ કન્ઝ્યુમર આધાર રાખે છે; DoclingAdapter અને ભવિષ્યનો કોઈપણ DocumentAIAdapter / VLMAdapter ફક્ત તેમના બેકએન્ડના native આઉટપુટને તે કરારમાં મેપ કરવા માટે જ અસ્તિત્વ ધરાવે છે. નવો એક્સ્ટ્રેક્ટર ઉમેરવાથી સ્કીમા કે કોઈપણ કન્ઝ્યુમર ક્યારેય બદલાતા નથી — તે એક adapter ક્લાસ ઉમેરે છે અને બીજું કંઈ નહીં.

ફ્રી-ફોર્મ JSON કરતાં discriminated unions. વાસ્તવિક દસ્તાવેજોમાં ફકરા, હેડિંગ, ટેબલ, આકૃતિઓ અને યાદીઓ મિશ્રિત હોય છે. દરેકને literal block_type ટેગ સાથે Pydantic મોડેલ તરીકે એન્કોડ કરવાથી list[ContentBlock | TableBlock] યોગ્ય રીતે deserialize થાય છે અને સાથે સાથે તમને TableBlock.headers અથવા ContentBlock.level જેવા variant-વિશિષ્ટ ફીલ્ડ્સનો typed ઍક્સેસ મળે છે. ફ્રી-ફોર્મ dicts એ જ ડેટા આપે છે પરંતુ validation અને IDE સપોર્ટ ગુમાવે છે.

Provenance એક પ્રથમ-વર્ગનું ફીલ્ડ છે, sidecar લોગ નહીં. કારણ કે દરેક બ્લોક પોતાનું Provenance (extractor, version, page, bbox, confidence, quality_flags) ધરાવે છે, ડાઉનસ્ટ્રીમ કોડ પ્રત્યેક બ્લોક માટે નિર્ણયો લઈ શકે છે — ઓછા confidence વાળું VLM આઉટપુટ કાઢી નાખવું, ફ્લેગ થયેલા બ્લોક્સને સમીક્ષા માટે ફરીથી રૂટ કરવા, RAG જવાબમાં citations attribute કરવા — બાહ્ય મેટાડેટા સ્ટોર સાથે ફરીથી જોડાયા વિના. નીચેનું Code Walkthrough બરાબર બતાવે છે કે આ ત્રણેય વિચારો સ્કીમામાં કેવી રીતે દેખાય છે અને DoclingAdapter તેમને કેવી રીતે ઉત્પન્ન કરે છે.

Code Walkthrough

Pydantic સ્કીમા વ્યાખ્યાયિત કરવી

દસ્તાવેજ સામગ્રીની polymorphic પ્રકૃતિને સંભાળવા માટે discriminated unions સાથે Pydantic મોડેલનો ઉપયોગ કરો:

Code snippetpython
1from pydantic import BaseModel, Field 2from typing import Literal, Optional 3from datetime import datetime 4from enum import Enum 5 6class BlockType(str, Enum): 7 PARAGRAPH = "paragraph" 8 HEADING = "heading" 9 TABLE = "table" 10 FIGURE = "figure" 11 LIST_ITEM = "list_item" 12 CODE = "code" 13 14class Provenance(BaseModel): 15 extractor: str 16 extractor_version: str 17 page_number: Optional[int] = None 18 bbox: Optional[tuple[float, float, float, float]] = None 19 confidence: Optional[float] = None 20 quality_flags: list[str] = Field(default_factory=list) 21 22class TableCell(BaseModel): 23 text: str 24 row_span: int = 1 25 col_span: int = 1 26 27class TableBlock(BaseModel): 28 block_type: Literal["table"] = "table" 29 headers: list[str] 30 rows: list[list[TableCell]] 31 provenance: Provenance 32 33class ContentBlock(BaseModel): 34 block_type: BlockType 35 text: str 36 level: Optional[int] = None 37 children: list["ContentBlock"] = Field(default_factory=list) 38 provenance: Provenance 39 40class DocumentPage(BaseModel): 41 page_number: int 42 blocks: list[ContentBlock | TableBlock] 43 44class UnifiedDocument(BaseModel): 45 document_id: str 46 source_uri: str 47 schema_version: str = "1.0" 48 format: str 49 page_count: int 50 extraction_timestamp: datetime 51 pages: list[DocumentPage] 52 metadata: dict = Field(default_factory=dict)
  • લાઇન 6-12: BlockType enum તમારી પાઇપલાઇન જે બધા કન્ટેન્ટ પ્રકારો સંભાળે છે તે વ્યાખ્યાયિત કરે છે. ફ્રી-ફોર્મ strings ને બદલે enum નો ઉપયોગ validation સમયે ટાઇપો પકડે છે અને ડાઉનસ્ટ્રીમ પ્રોસેસર્સમાં exhaustive pattern matching સક્ષમ કરે છે.
  • લાઇન 14-20: Provenance મોડેલ ટ્રેક કરે છે કે કયા એક્સ્ટ્રેક્ટરે દરેક બ્લોક બનાવ્યો. quality_flags યાદી extraction દરમિયાન શોધાયેલી સમસ્યાઓ નોંધે છે — "low_confidence", "possible_hallucination", "truncated" — જે ડાઉનસ્ટ્રીમમાં quality-aware પ્રોસેસિંગ સક્ષમ કરે છે.
  • લાઇન 28-31: TableBlock block_type: Literal["table"] સાથે discriminated union પેટર્નનો ઉપયોગ કરે છે જેથી Pydantic મિશ્રિત કન્ટેન્ટ બ્લોક યાદીઓને યોગ્ય રીતે deserialize કરી શકે. Headers અને rows TableCell ઑબ્જેક્ટ્સનો ઉપયોગ કરે છે જે row_span અને col_span દ્વારા merged cells ને સપોર્ટ કરે છે.
  • લાઇન 33-37: ContentBlock children ફીલ્ડ દ્વારા recursive nesting ને સપોર્ટ કરે છે, જે વાસ્તવિક દસ્તાવેજોની hierarchical રચનાને પ્રતિબિંબિત કરે છે જ્યાં sections માં subsections અને subsections માં ફકરા હોય છે.
  • લાઇન 43-52: ટોપ-લેવલ UnifiedDocument દસ્તાવેજ-સ્તરનો મેટાડેટા, દરેકમાં બ્લોક્સ ધરાવતા પેજોની યાદી, અને ઉત્ક્રાંતિ માટે schema_version સમાવે છે.

Extraction Adapters બનાવવા

દરેક adapter એક્સ્ટ્રેક્ટર-વિશિષ્ટ આઉટપુટને એકીકૃત મોડેલમાં રૂપાંતરિત કરે છે:

Code snippetpython
1class DoclingAdapter: 2 EXTRACTOR_NAME = "docling" 3 EXTRACTOR_VERSION = "2.0" 4 5 def to_unified_document( 6 self, result, source_uri: str 7 ) -> UnifiedDocument: 8 doc = result.document 9 pages = [] 10 11 current_page_blocks = [] 12 current_page = 1 13 14 for item, level in doc.iterate_items(): 15 page_num = item.prov[0].page_no if item.prov else current_page 16 17 if page_num != current_page and current_page_blocks: 18 pages.append(DocumentPage( 19 page_number=current_page, 20 blocks=current_page_blocks, 21 )) 22 current_page_blocks = [] 23 current_page = page_num 24 25 provenance = Provenance( 26 extractor=self.EXTRACTOR_NAME, 27 extractor_version=self.EXTRACTOR_VERSION, 28 page_number=page_num, 29 bbox=item.prov[0].bbox.as_tuple() if item.prov else None, 30 confidence=None, 31 ) 32 33 class_name = item.__class__.__name__ 34 if class_name == "SectionHeaderItem": 35 block = ContentBlock( 36 block_type=BlockType.HEADING, 37 text=item.text, 38 level=item.level, 39 provenance=provenance, 40 ) 41 elif class_name == "TableItem": 42 df = item.export_to_dataframe() 43 block = TableBlock( 44 headers=list(df.columns), 45 rows=[ 46 [TableCell(text=str(cell)) for cell in row] 47 for _, row in df.iterrows() 48 ], 49 provenance=provenance, 50 ) 51 else: 52 block = ContentBlock( 53 block_type=BlockType.PARAGRAPH, 54 text=item.text if hasattr(item, "text") else "", 55 provenance=provenance, 56 ) 57 58 current_page_blocks.append(block) 59 60 if current_page_blocks: 61 pages.append(DocumentPage( 62 page_number=current_page, 63 blocks=current_page_blocks, 64 )) 65 66 return UnifiedDocument( 67 document_id=self._generate_id(source_uri), 68 source_uri=source_uri, 69 format=str(result.input.format), 70 page_count=len(doc.pages) if hasattr(doc, "pages") else len(pages), 71 extraction_timestamp=datetime.utcnow(), 72 pages=pages, 73 ) 74 75 def _generate_id(self, uri: str) -> str: 76 import hashlib 77 return hashlib.sha256(uri.encode()).hexdigest()[:16]
  • લાઇન 1-3: દરેક adapter પોતાની એક્સ્ટ્રેક્ટર ઓળખ class constants તરીકે જાહેર કરે છે, જે આ adapter દ્વારા પ્રોસેસ થયેલા બધા દસ્તાવેજોમાં સુસંગત provenance ટ્રેકિંગ સુનિશ્ચિત કરે છે.
  • લાઇન 14-23: કન્ટેન્ટ બ્લોક્સને પેજ નંબર પ્રમાણે જૂથબદ્ધ કરો. Docling નું iterate_items() તત્વોને વાંચન ક્રમમાં આપે છે, પરંતુ પેજ બદલાવ provenance મેટાડેટામાંથી શોધવા પડે છે.
  • લાઇન 25-31: દરેક બ્લોક માટે Docling ના આઉટપુટમાંથી એક્સ્ટ્રેક્ટરનું નામ, વર્ઝન, પેજ નંબર અને bounding box સાથે Provenance ઑબ્જેક્ટ્સ બનાવો.
  • લાઇન 33-52: Docling તત્વ પ્રકારોને એકીકૃત મોડેલના બ્લોક પ્રકારોમાં મેપ કરો. SectionHeaderItem તેના level ને જાળવી રાખીને હેડિંગ બને છે. TableItem DataFrame માં export થાય છે અને પછી યોગ્ય TableCell ઑબ્જેક્ટ્સ સાથે canonical TableBlock ફોર્મેટમાં રૂપાંતરિત થાય છે. બાકીના બધા તત્વો ડિફૉલ્ટ રૂપે ફકરા બને છે.
  • લાઇન 65-72: source URI માંથી મેળવેલા deterministic ID સાથે અંતિમ UnifiedDocument બનાવો, જે સુનિશ્ચિત કરે છે કે idempotent પુનઃ-extraction એ જ દસ્તાવેજ ID ઉત્પન્ન કરે.

Adapter પેટર્ન તમારી પાઇપલાઇનને કોઈપણ એક extraction બેકએન્ડથી અલગ કરે છે. નવો એક્સ્ટ્રેક્ટર ઉમેરવા માટે કોઈપણ ડાઉનસ્ટ્રીમ કન્ઝ્યુમર કોડમાં ફેરફાર કર્યા વિના માત્ર એક નવો adapter ક્લાસ લખવો પડે છે.

Loading diagram...

શિસ્ત-વિશિષ્ટ ઉપયોગ

શું કરવું અને શું ન કરવું

હવે જ્યારે તમે અમલીકરણમાંથી પસાર થઈ ગયા છો, નીચેની પદ્ધતિઓ ટકાઉ અભિગમને નાજુક અભિગમથી અલગ પાડે છે.

શું કરવું

  1. દરેક કન્ટેન્ટ-બ્લોક variant ને Literal["..."] block_type સાથે ટેગ કરો જેથી Pydantic મિશ્રિત list[ContentBlock | TableBlock] સામગ્રીને અસ્પષ્ટતા વિના deserialize કરી શકે અને કન્ઝ્યુમર્સ ટેગ પર dispatch કરી શકે.
  2. દરેક બ્લોક પર Provenance ફરજિયાત બનાવો — એક્સ્ટ્રેક્ટરનું નામ, વર્ઝન, પેજ નંબર, ઉપલબ્ધ હોય ત્યાં bbox, અને quality_flags — જેથી ડાઉનસ્ટ્રીમ re-ranking, debugging અને citation માર્ગોને ક્યારેય બાહ્ય મેટાડેટા સ્ટોર સાથે ફરીથી જોડાવું ન પડે.
  3. જ્યારે પણ તમે બ્લોક પ્રકાર ઉમેરો અથવા કોઈ ફીલ્ડનો અર્થ બદલો ત્યારે UnifiedDocument પર schema_version વધારો, અને કન્ઝ્યુમર્સને version પર બ્રાન્ચ કરવાનું શીખવો (ક્યારેય એક્સ્ટ્રેક્ટર પર નહીં).

શું ન કરવું

  1. if extractor == "docling" બ્રાન્ચિંગને adapter સીમાની બહાર લીક થવા ન દો — એકવાર UnifiedDocument અસ્તિત્વમાં આવે પછી કોઈપણ chunker, embedder કે indexer ને ખબર ન હોવી જોઈએ કે કયા બેકએન્ડે તેને બનાવ્યું.
  2. અજાણ્યા એક્સ્ટ્રેક્ટર તત્વ પ્રકારોને ફ્રી-ફોર્મ dict blobs માં ન નાખો; તેમને સૌથી નજીકના canonical BlockType માં મેપ કરો (ડિફૉલ્ટ PARAGRAPH) જેથી discriminated union exhaustive રહે અને validation અર્થપૂર્ણ રહે.
  3. Provenance ને વૈકલ્પિક ન બનાવો કે તેને sidecar લોગમાં ન સંગ્રહો — બ્લોક-સ્થાનિક provenance એ જ મુખ્ય કારણ છે કે normalization પછી quality-aware પ્રોસેસિંગ કામ કરે છે.

3 hands-on labs come with this lesson — real code, in a cloud IDE. Create a free account to run them. No card.

Free account · no card · straight to the labs

Or get the full path — from

Listen to this lesson

Audio overviews of this lesson's labs and its chapter, from GenBodha Bytes.

More free lessons in GenAI Data Pipelines

All free lessons in GenAI Data Engineering →