使用 Gemini 和 Knowledge Catalog 管理非結構化暗資料

1. 簡介

超過 80% 的企業資料都是非結構化格式,例如儲存在 Cloud Storage 中的 PDF 工程手冊、標準作業程序 (SOP)、法規遵循認證和設備示意圖。由於傳統目錄只會為關聯式資料表結構定義建立索引,這些文件仍屬於「暗資料」:企業搜尋無法找到,與結構化目錄資料表沒有關聯,且未經過法規遵循驗證。

當稽核人員或 AI 代理程式詢問「哪些硬體 SKU 需要 UL-153 安全認證,以及這些 SKU 的熱危害等級為何?」時,只會為 SQL 表格建立索引的目錄就無法回答。事實資訊鎖在 Cloud Storage 中的非結構化 PDF 檔案內。

架構解決方案:多模態中繼資料即程式碼

在本程式碼研究室中,您將在 Google Cloud 中建構端對端「Metadata-as-Code」管道,將 Cloud Storage 中的非結構化 PDF 手冊,透過 Gemini Enterprise Agent Platform (Google GenAI SDK,google-genai) 轉換為符合結構定義的 Knowledge Catalog 資產:

非結構化暗資料管理流程

  1. 檔案集註冊 (EntryGroup 和 Entry):將 Cloud Storage 中的非結構化 PDF 分組,成為 Knowledge Catalog 中受管理的邏輯檔案集項目。
  2. Gemini 多模態擷取 (Pydantic 架構):在原始 PDF 位元組上叫用 Gemini Enterprise Agent Platform,並進行確定性 response_schema 驗證。
  3. 控制平面層面繫結:將經過驗證的作業和法規遵循 JSON 酬載,以強型別 Aspect 例項的形式附加至目錄項目。
  4. 統一目錄探索:透過 SearchEntriesRequest 探索非結構化 PDF 檔案集及其擷取的中繼資料。

重要概念:Knowledge Catalog 控制層基本類型

假設您的團隊在 Cloud Storage 中儲存了數百份 PDF 產品手冊。為了方便在知識目錄中尋找及查看這些檔案,請將五個建構區塊視為辦公室檔案櫃中的紙本手冊:

  • EntryGroup (一個團隊專用的上鎖櫃子):上鎖的櫃子。只有具備權限的隊友 (roles/dataplex.entryCreator) 才能在資料夾中放置檔案。你必須先清空櫃子中的所有資料夾,才能移除櫃子。
  • EntryType (資料夾類別標籤):標籤,可讓使用者瞭解資料夾代表的資產類型,例如「PDF 手冊集」而非「資料庫表格」。
  • Entry (含有儲存空間連結的檔案資料夾):PDF 手冊的目錄資料夾。這個資料夾不會存放大量紙本副本,而是存放指向實際 PDF 檔案所在 Cloud Storage bucket (gs://...) 的位置資訊卡。
  • AspectType (空白檢查清單表單):可重複使用的空白表單,內含預先列印的方塊 (例如「安全檢查清單」,要求以文字形式輸入 operational_hazard_level,並以數字形式輸入 confidence_score (範圍為 0.0 至 1.0))。您只要建立一次空白表單,就能在數千個資料夾中重複使用。
  • Aspect (已填寫的核對清單附加至資料夾):Gemini Enterprise Agent Platform 讀取 PDF 手冊並填寫空白處後,完成的核對清單會夾在 Entry 資料夾上。由於每個核對清單都有自己的鍵 (aspect_keys),工程團隊和法律團隊可以各自將核對清單附加至同一個資料夾,而不會覆寫彼此的工作。

所有五個區域資源都位於 projects/{project}/locations/{location}/ 下方:

原始路徑和相對路徑

Knowledge Catalog 中的角色

刪除和 IAM 規則

EntryGroup
.../entryGroups/{id}

邏輯容器,可將項目分組並控管團隊存取權 (roles/dataplex.entryCreator)。

刪除前必須不含任何子項 Entry 資源。

EntryType
.../entryTypes/{id}

資產類別的類別定義 (entry.entry_type,例如非結構化 PDF 檔案集)。

請先移除使用這類型的所有項目,再刪除類型。

Entry (Fileset)
.../entries/{entry_id}

目錄記錄,將實體 Cloud Storage URI (entry_source.resource) 連結至邏輯名稱 (gcs:{bucket})。

位於 EntryGroup 內,並保存附加的 Aspect 記錄。

AspectType
.../aspectTypes/{id}

可重複使用的結構定義範本,定義型別 record_fields (string、enum、double) 和以 1 為基準的欄位編號 (index)。

每個區域建立一次,並在所有項目中共用。

Aspect
Entry.aspects[key]

與 AspectType 相符的已驗證 JSON 中繼資料,透過 UpdateEntryRequest 附加至 Entry。

使用 update_mask 和 aspect_keys 更新,以免個別管道互相覆寫。

建構項目

使用 codelabs/metadata-as-code-lab 中的隨附指令碼,執行並檢查 7 階段的管道:

  1. schemas.py - 雙 Pydantic BaseModel 結構定義 (DarkDataExtractedMetadata 和 DocumentGovernanceMetadata) 會與 Knowledge Catalog AspectType 範本 1:1 配對。
  2. step2_provision.py - 冪等佈建自訂 EntryGroup、EntryType 和 AspectType 藍圖。
  3. step3_register_fileset.py - 將 LUM-LIG-DES-8G8J_manual.pdf 上傳至 Cloud Storage,並註冊為受管理檔案集 Entry。
  4. step4_extract_metadata.py - 使用 google-genai 動態探索旗艦 Gemini 模型,並擷取多模態 PDF 檔案。
  5. step5_attach_aspects.py、step6_search_catalog.py 和 step7_verify_assertions.py:手術層面附件、指數退避目錄搜尋,以及端對端控制層斷言。

必要條件和所需項目

  • 已啟用帳單的 Google Cloud 專案,以及 Google Cloud Shell (預先安裝 gcloud、git、python3 和 pip)。
  • 管理 Knowledge Catalog 資源 (roles/dataplex.admin)、佈建 Cloud Storage bucket 和物件 (roles/storage.admin),以及叫用 Gemini Enterprise Agent Platform (roles/aiplatform.user) 的 IAM 權限。
  • 網路瀏覽器,例如 Google Chrome。

2. 設定和需求條件

在 Cloud Shell 中複製隨附存放區

開啟 Google Cloud 控制台,啟動 Cloud Shell,然後對 codelabs/metadata-as-code-lab 執行稀疏結帳,其中包含範例 PDF 工程手冊 (LUM-LIG-DES-8G8J_manual.pdf) 和所有 Python 指令碼:

git clone --branch main --depth 1 --filter=blob:none --sparse \
  https://github.com/GoogleCloudPlatform/devrel-demos.git
cd devrel-demos
git sparse-checkout set codelabs/metadata-as-code-lab
cd codelabs/metadata-as-code-lab
ls -la

設定環境變數

在 Cloud Shell 中設定專案 ID、區域控制層位置和目標 Cloud Storage bucket:

export PROJECT_ID="${PROJECT_ID:-$(gcloud config get-value project)}"
gcloud config set project $PROJECT_ID

export REGION="us-central1"
export BUCKET_NAME="${PROJECT_ID}-dark-data-manuals"
export GEMINI_LOCATION="global"

echo "Configured Project ID        : $PROJECT_ID"
echo "Knowledge Catalog Region     : $REGION"
echo "Gemini Endpoint Location     : $GEMINI_LOCATION"
echo "Target Cloud Storage Bucket  : $BUCKET_NAME"

啟用 API 並安裝依附元件

啟用必要的 Google Cloud API,並從 requirements.txt 安裝 Python 依附元件 (google-genai、google-cloud-dataplex、pydantic 和 pandas):

gcloud services enable \
  dataplex.googleapis.com \
  storage.googleapis.com \
  aiplatform.googleapis.com \
  --quiet
python3 -m pip install --upgrade --quiet --user --break-system-packages -r requirements.txt

3. 定義 Pydantic 結構定義和面向範本

schemas.py 定義了兩個互補的合約,可將用戶端 Pydantic 驗證與伺服器端 Knowledge Catalog AspectType 範本配對:

  1. DarkDataExtractedMetadata:從 PDF 擷取的作業屬性 (文件標題、網域本體、安全危害程度、撰寫/版本出處、執行摘要和擷取信賴分數)。
  2. DocumentGovernanceMetadata:法規遵循代碼 (UL-153、FCC 第 15 條、CE-LVD、RoHS)、管理員、保留政策,以及用於將 PDF 手冊連結至下游目錄表的參考表 ID 字串 (lakehouse_cross_ref_table)。

檢查 schemas.py

首先,DarkDataExtractedMetadata 會使用 Pydantic Field 限制 (ge=0.0, le=1.0,表示大於/小於或等於,位於 confidence_score) 來限制 Gemini Enterprise Agent Platform 的 JSON 輸出內容:

class DarkDataExtractedMetadata(BaseModel):
    document_title: str = Field(
        description="Official document title or product model name extracted from the PDF manual."
    )
    # ... domain_ontology, operational_hazard_level, author_provenance ...
    version_provenance: str = Field(
        description="Document version, revision code, or publication standard identifier."
    )
    # ... document_summary ...
    confidence_score: float = Field(
        ge=0.0,
        le=1.0,
        description="AI extraction confidence metric between 0.0 and 1.0.",
    )

其次,extracted_aspect_template 會反映這些確切的欄位名稱,並將 Python 的 str 和 float 型別對應至Knowledge Catalog的 string 和 double record_fields。請注意,AspectType.MetadataTemplate protobuf 合約要求 record_fields 中的每個欄位都必須宣告專屬的嚴格正整數 (以 1 為基底) index (1 至 7),如果省略或重複 index,create_aspect_type 就會失敗並顯示 400 INVALID_ARGUMENT:

extracted_aspect_template = {
    "name": "DarkDataExtractedMetadata",
    "type": "record",
    "record_fields": [
        {
            "name": "document_title",
            "type": "string",
            "index": 1,
            "annotations": {"description": "Official document title or product model name."},
        },
        # ... fields 2 through 6 (string) ...
        {
            "name": "confidence_score",
            "type": "double",
            "index": 7,
            # ... annotations ...
        },
    ],
}

驗證結構定義模組

執行 schemas.py,驗證環境設定並編譯兩個結構定義:

python3 schemas.py
Configured Project ID         : your-project-id
Knowledge Catalog Region      : us-central1
Gemini Endpoint Location      : global
Governed Fileset Storage URI  :
  gs://your-project-id-dark-data-manuals/sop-pdfs/LUM-LIG-DES-8G8J_manual.pdf
Target Fileset Entry ID       : sop-manuals-fileset-lum8g8j
✓ Defined DarkDataExtractedMetadata and DocumentGovernanceMetadata
  Pydantic schemas.
✓ Prepared Knowledge Catalog AspectType metadata templates.

4. 佈建命名空間和自訂切面類型

註冊檔案集項目或附加中繼資料前,step2_provision.py 會在 Knowledge Catalog 中佈建三個控制層資源:

  • EntryGroup:dark-data-fileset-group
  • EntryType:unstructured-pdf-fileset
  • AspectType藍圖:dark-data-extracted-metadata 和 document-governance-aspect

step2_provision.py 中的等冪佈建

step2_provision.py 中的每個建立呼叫都會將長時間執行的作業 (op.result()) 包裝在 try / except AlreadyExists 區塊中,因此指令碼可以安全地重新執行,不會因現有資源而失敗:

# Provision AspectType 1: dark-data-extracted-metadata
try:
    at1_op = catalog_client.create_aspect_type(
        parent=parent_location,
        aspect_type_id=EXTRACTED_ASPECT_TYPE_ID,
        aspect_type=AspectType(
            name=extracted_aspect_type_name,
            display_name="Dark Data Multimodal Extracted Metadata",
            # ... description ...
            metadata_template=extracted_aspect_template,
        ),
    )
    if hasattr(at1_op, "result"):
        at1_op.result()
    print(f"Created AspectType: {extracted_aspect_type_name}")
except AlreadyExists:
    print(f"AspectType already exists; retrieving live resource: {extracted_aspect_type_name}")

live_extracted_aspect_type = catalog_client.get_aspect_type(name=extracted_aspect_type_name)

註冊 metadata_template=extracted_aspect_template 後,Knowledge Catalog 會在 API 邊界強制執行結構定義,後續任何將字串傳遞至 confidence_score (double) 的 update_entry() 呼叫都會遭到拒絕。

執行「step2_provision.py」

執行佈建指令碼,並確認自訂切面類型已在您所在區域註冊:

python3 step2_provision.py
gcloud dataplex aspect-types list --location=$REGION
Created EntryGroup:
  projects/your-project-id/locations/us-central1/entryGroups/
  dark-data-fileset-group
Created EntryType :
  projects/your-project-id/locations/us-central1/entryTypes/
  unstructured-pdf-fileset
Created AspectType:
  projects/your-project-id/locations/us-central1/aspectTypes/
  dark-data-extracted-metadata
Created AspectType:
  projects/your-project-id/locations/us-central1/aspectTypes/
  document-governance-aspect
✓ All Knowledge Catalog namespaces and AspectType blueprints are active.

5. 註冊非結構化檔案集項目

Knowledge Catalog 會將 Cloud Storage (gs://YOUR_BUCKET/sop-pdfs/*.pdf) 中相關的文件分組為邏輯 Fileset Entries,而不是在目錄中塞滿數百萬個獨立的檔案指標,可透過自動探索掃描 (DataScanServiceClient) 或直接以程式輔助方式註冊 (CatalogServiceClient.create_entry) 達成。

step3_register_fileset.py 如何將儲存空間對應至目錄

step3_register_fileset.py 上傳 LUM-LIG-DES-8G8J_manual.pdf至 gs://${BUCKET_NAME}/sop-pdfs/LUM-LIG-DES-8G8J_manual.pdf,並註冊受管理 Entry:

fully_qualified_fileset_uri = f"gcs:{BUCKET_NAME}"
fileset_entry_spec = Entry(
    name=fileset_entry_name,
    entry_type=entry_type_resource_name,
    fully_qualified_name=fully_qualified_fileset_uri,
    entry_source=EntrySource(
        resource=VIRTUAL_STORAGE_URI,
        system="Cloud Storage",
        platform="Google Cloud",
        display_name="LUM-LIG-DES-8G8J Unstructured SOP & Technical Manual Fileset",
        # ... description ...
        location=REGION,
    ),
)

create_entry_op = catalog_client.create_entry(
    parent=entry_group_resource_name,
    entry_id=FILESET_ENTRY_ID,
    entry=fileset_entry_spec,
)

請注意 Entry 上兩個 URI 欄位的不同角色:

  • fully_qualified_name=fully_qualified_fileset_uri (f"gcs:{BUCKET_NAME}"):使用 Knowledge Catalog 要求的標準 gcs:{bucket_name} FQN 結構,將邏輯目錄項目與基礎 Cloud Storage bucket 建立關聯。
  • entry_source.resource=VIRTUAL_STORAGE_URI:儲存下游擷取工作人員檢查的實體 gs:// 物件 URI。

執行「step3_register_fileset.py」

python3 step3_register_fileset.py
Created Cloud Storage bucket:
  gs://your-project-id-dark-data-manuals in us-central1
Uploaded unstructured PDF manual to:
  gs://your-project-id-dark-data-manuals/sop-pdfs/LUM-LIG-DES-8G8J_manual.pdf
Registered governed Fileset Entry:
  projects/your-project-id/locations/us-central1/entryGroups/
  dark-data-fileset-group/entries/sop-manuals-fileset-lum8g8j
  -> Fully Qualified Name : gcs:your-project-id-dark-data-manuals
  -> Physical Resource URI:
  gs://your-project-id-dark-data-manuals/sop-pdfs/LUM-LIG-DES-8G8J_manual.pdf
  -> Source System        : Cloud Storage

6. 使用 Gemini 擷取文件元資料

step4_extract_metadata.py 會讀取原始 PDF 位元組,並使用兩種主要模式叫用 Gemini Enterprise Agent Platform (genai_client.models.generate_content):

1. 動態探索旗艦機型

指令碼會查詢 genai_client.models.list() 並依 (is_ga_channel, semantic_version_tuple) 排序候選版本,選取最新的正式發布 (GA) flash 模型,而不是對脆弱的模型版本字串進行硬式編碼:

genai_client = genai.Client(
    vertexai=True,
    project=PROJECT_ID,
    location=GEMINI_LOCATION,
)

for model_obj in genai_client.models.list():
    raw_id = (model_obj.name or "").split("/")[-1]
    # Filter to flagship Gemini Flash models (version >= 3.0) and prioritize GA over preview
    ...
    discovered_candidates.append((is_ga_channel, ver_tuple, raw_id))

discovered_candidates.sort(key=lambda item: (item[0], item[1]), reverse=True)

2. 受結構定義限制的多模態擷取 (Part.from_bytes + response_schema)

傳遞 response_mime_type="application/json" 和 response_schema=DarkDataExtractedMetadata (後接 DocumentGovernanceMetadata) 會直接將權杖解碼限制為 Pydantic JSON 結構定義:

with open(LOCAL_PDF_PATH, "rb") as pdf_file:
    raw_pdf_bytes = pdf_file.read()

extraction_response = genai_client.models.generate_content(
    model=candidate_model,
    contents=[
        types.Part.from_bytes(data=raw_pdf_bytes, mime_type="application/pdf"),
        extraction_prompt,
    ],
    config=types.GenerateContentConfig(
        response_mime_type="application/json",
        response_schema=DarkDataExtractedMetadata,
        temperature=0.1,
    ),
)

extracted_dark_data = json.loads(extraction_response.text)
validated_extraction = DarkDataExtractedMetadata(**extracted_dark_data)

執行「step4_extract_metadata.py」

python3 step4_extract_metadata.py
Dynamically discovered active GA model currency :
  gemini-3.8-flash (version (3, 8))
Loaded unstructured PDF manual at: LUM-LIG-DES-8G8J_manual.pdf
Read unstructured PDF document (1184 bytes).
  Executing multimodal extraction with gemini-3.8-flash...
✓ Multimodal extraction completed using model: gemini-3.8-flash
✓ Governance classification completed using model: gemini-3.8-flash

--- Extracted Multimodal Operational Metadata ---
Extracted Attribute    Multimodal Value
document_title    LUM-LIG-DES-8G8J Contemporary Linen Desk Lamp
domain_ontology    Electrical Hardware & Lighting Systems
operational_hazard_level    MEDIUM
author_provenance    Global Hardware Safety & Lighting Systems Engin
version_provenance    Rev 4.2
document_summary    This document outlines standard operating proce
confidence_score    0.99

--- Extracted Governance & Inventory Cross-Reference Metadata ---
Governance Attribute    Classification Value
compliance_classifications    UL-153 Portable Luminaires, FCC Part 15, CE-LVD
stewardship_owner    Global Hardware Safety & Lighting Systems Engin
retention_policy    10-Year Active Product Lifecycle Archival
lakehouse_cross_ref_table    lakehouse.your-project-id.retail_hardware
governance_status    VERIFIED_PRODUCTION

7. 將驗證過的切面附加至目錄項目

step5_attach_aspects.py 會透過 catalog_client.update_entry() 將經過驗證的 JSON 酬載 (extracted_metadata.json 和 governance_metadata.json) 繫結至 sop-manuals-fileset-lum8g8j 項目。

step5_attach_aspects.py 中的手術式變異

extracted_aspect_key = f"{PROJECT_ID}.{REGION}.{EXTRACTED_ASPECT_TYPE_ID}"
governance_aspect_key = f"{PROJECT_ID}.{REGION}.{GOVERNANCE_ASPECT_TYPE_ID}"

updated_entry_payload = Entry(
    name=fileset_entry_name,
    entry_type=entry_type_resource_name,
    fully_qualified_name=fully_qualified_fileset_uri,
    aspects={
        extracted_aspect_key: Aspect(
            aspect_type=extracted_aspect_type_name,
            data=extracted_data,
        ),
        governance_aspect_key: Aspect(
            aspect_type=governance_aspect_type_name,
            data=governance_data,
        ),
    },
)

update_op = catalog_client.update_entry(
    request=dataplex_v1.UpdateEntryRequest(
        entry=updated_entry_payload,
        update_mask={"paths": ["aspects"]},
        aspect_keys=[extracted_aspect_key, governance_aspect_key],
    )
)

同時傳遞 和 update_mask={"paths": ["aspects"]}aspect_keys=[extracted_aspect_key, governance_aspect_key] 範圍,可將變動嚴格限制在這兩個切面鍵上,讓 entry_source 和其他團隊擁有的任何切面保持不變。

執行「step5_attach_aspects.py」

python3 step5_attach_aspects.py
Successfully bound 2 Aspect(s) to Entry:
  projects/your-project-id/locations/us-central1/entryGroups/
  dark-data-fileset-group/entries/sop-manuals-fileset-lum8g8j

=== Live Attached Aspect: document-governance-aspect ===
Catalog Field    Authoritative Stored Value
compliance_classifications    UL-153 Portable Luminaires, FCC Part 15, CE-LVD
stewardship_owner    Global Hardware Safety & Lighting Systems Engin
retention_policy    10-Year Active Product Lifecycle Archival
lakehouse_cross_ref_table    lakehouse.your-project-id.retail_hardware
governance_status    VERIFIED_PRODUCTION

=== Live Attached Aspect: dark-data-extracted-metadata ===
Catalog Field    Authoritative Stored Value
document_title    LUM-LIG-DES-8G8J Contemporary Linen Desk Lamp
domain_ontology    Electrical Hardware & Lighting Systems
operational_hazard_level    MEDIUM
author_provenance    Global Hardware Safety & Lighting Systems Engin
version_provenance    Rev 4.2
document_summary    This document outlines standard operating proce
confidence_score    0.99

雙軌驗證

  • 第 1 軌 (CLI 驗證):透過 gcloud 檢查所有附加層面的項目:
    gcloud dataplex entries describe sop-manuals-fileset-lum8g8j \
      --entry-group=dark-data-fileset-group \
      --location=$REGION \
      --view=ALL
    
  • 軌跡 2 (控制台 UI 檢查):
    1. 開啟 Knowledge Catalog Search Console,搜尋 LUM-LIG-DES-8G8J Unstructured SOP & Technical Manual Fileset (sop-manuals-fileset-lum8g8j),然後從結果中選取相符項目。
    2. 在「Optional tags and aspects」(選用標記和切面) 下方,確認「Dark Data Multimodal Extracted Metadata」(暗資料多模態擷取中繼資料) 和「Document Governance & Inventory Cross-Reference Aspect」(文件管理和清查交叉參照切面) 都顯示擷取的危害程度 (MEDIUM)、法規遵循代碼 (UL-153, FCC Part 15, CE-LVD, RoHS) 和交叉比對表字串 (lakehouse_cross_ref_table)。

8. 在目錄中查詢受管理的不公開資料

附加自訂面向後,即可透過 Knowledge Catalog 搜尋 (catalog_client.search_entries()) 探索 Cloud Storage 中的非結構化 PDF 手冊。

意見調查 search_entries() (step6_search_catalog.py)

step6_search_catalog.py 輪詢 search_entries(),並透過 .endswith() 讀取標準化層面鍵:

search_query = f"entrygroup={entry_group_resource_name} name:{FILESET_ENTRY_ID}"

while elapsed_seconds < max_wait_seconds:
    catalog_search_results = list(
        catalog_client.search_entries(
            request=dataplex_v1.SearchEntriesRequest(
                name=parent_location,
                query=search_query,
            )
        )
    )
    if catalog_search_results:
        break
    backoff_seconds = min(5 * (2 ** attempt), 30)
    time.sleep(backoff_seconds)
    ...

for k, asp in detailed_entry.aspects.items():
    if k.endswith(f".{REGION}.{EXTRACTED_ASPECT_TYPE_ID}"):
        print(f"    Domain Ontology     : {asp.data.get('domain_ontology')}")
        print(f"    Hazard Level        : {asp.data.get('operational_hazard_level')}")
    elif k.endswith(f".{REGION}.{GOVERNANCE_ASPECT_TYPE_ID}"):
        print(f"    Compliance Codes    : {asp.data.get('compliance_classifications')}")
        print(f"    Lakehouse Join Table: {asp.data.get('lakehouse_cross_ref_table')}")

執行「step6_search_catalog.py」

python3 step6_search_catalog.py
Executing Knowledge Catalog search query:
  'entrygroup=projects/your-project-id/locations/us-central1/entryGroups/
  dark-data-fileset-group name:sop-manuals-fileset-lum8g8j'...

✓ Discovered 1 governed asset(s) in Knowledge Catalog:

[1] Entry Resource Name :
    projects/your-project-id/locations/us-central1/entryGroups/
    dark-data-fileset-group/entries/sop-manuals-fileset-lum8g8j
    Fully Qualified Name: gcs:your-project-id-dark-data-manuals
    Entry Type :
    projects/your-project-id/locations/us-central1/entryTypes/
    unstructured-pdf-fileset
    Physical Storage URI:
    gs://your-project-id-dark-data-manuals/sop-pdfs/LUM-LIG-DES-8G8J_manual.pdf
    Domain Ontology     : Electrical Hardware & Lighting Systems
    Hazard Level        : MEDIUM
    Compliance Codes    : UL-153 Portable Luminaires, FCC Part 15, CE-LVD, RoHS
    Lakehouse Join Table:
    lakehouse.your-project-id.retail_hardware.luminaire_sku_inventory_iceberg

雙軌驗證

  • 第 1 階段 (CLI 搜尋):透過 gcloud 直接查詢搜尋索引:
    gcloud dataplex entries search "name:sop-manuals-fileset-lum8g8j" --project=$PROJECT_ID
    
  • 第 2 軌 (控制台 UI 自然語言搜尋):
    1. 開啟 Knowledge Catalog Search Console,然後搜尋 LUM-LIG-DES-8G8J 或 UL-153。
    2. 觀察Knowledge Catalog如何顯示受管理 Cloud Storage PDF 檔案集項目及其附加的中繼資料切面。

9. 驗證端對端控管聲明

step7_verify_assertions.py 會針對即時 Knowledge Catalog 控制層回應執行確定性斷言,以驗證 EntryGroup、EntryType、自訂 AspectType 藍圖、繫結 EntryView.ALL 構面有效負載 (confidence_score 範圍和 lakehouse_cross_ref_table 字串) 和搜尋索引探索是否都成功:

verify_eg = catalog_client.get_entry_group(name=entry_group_resource_name)
assert verify_eg.name == entry_group_resource_name, "EntryGroup resource name mismatch."

verify_entry = catalog_client.get_entry(
    request=dataplex_v1.GetEntryRequest(
        name=fileset_entry_name,
        view=dataplex_v1.EntryView.ALL,
    )
)
bound_extracted = next(
    (
        asp for k, asp in verify_entry.aspects.items()
        if k.endswith(f".{REGION}.{EXTRACTED_ASPECT_TYPE_ID}")
    ),
    None,
)
bound_governance = next(
    (
        asp for k, asp in verify_entry.aspects.items()
        if k.endswith(f".{REGION}.{GOVERNANCE_ASPECT_TYPE_ID}")
    ),
    None,
)

assert bound_extracted is not None, f"Aspect '{EXTRACTED_ASPECT_TYPE_ID}' not found on live Entry."
assert bound_governance is not None, (
    f"Aspect '{GOVERNANCE_ASPECT_TYPE_ID}' not found on live Entry."
)
assert (
    bound_extracted.data.get("document_title") == expected_extraction["document_title"]
), "Extracted title mismatch."
assert (
    0.0 <= float(bound_extracted.data.get("confidence_score", -1.0)) <= 1.0
), "Invalid confidence score in catalog."
assert (
    "lakehouse" in str(bound_governance.data.get("lakehouse_cross_ref_table", "")).lower()
), "Missing Lakehouse cross-reference."
...
assert len(search_results) >= 1, "Expected at least 1 search result in Knowledge Catalog."

執行「step7_verify_assertions.py」

python3 step7_verify_assertions.py && echo "Verification exit code: $?"
Running substantive end-to-end verification assertions...
✓ All 5 substantive verification assertions passed
  against live Google Cloud control plane state!
Verification exit code: 0

10. 設計事件導向管道

在 Cloud Shell 中以互動方式執行 Python 指令碼,是建構及測試控管合約的理想做法,但企業環境每天會從 ERP、產品生命週期管理 (PLM) 和供應商入口網站收到數千份新的或修訂的 PDF 工程手冊、供應商法規遵循認證和 SOP 文件。手動執行批次指令碼,會快速在文件上傳和目錄索引之間,產生治理盲點。

在實際運作中,平台團隊會將您在步驟 4 和 5 中執行的確切程式碼,做為零接觸的事件驅動管道:

事件導向的暗資料管道

  1. 持續擷取文件 (Cloud Storage → Eventarc):每當上傳新的 PDF 手冊,或新修訂版本覆寫 Cloud Storage 中的現有物件時,儲存空間會發出 google.cloud.storage.object.v1.finalized CloudEvent,其中包含儲存空間 ID、物件路徑和物件版本編號。
  2. 無狀態多模態擷取 (Eventarc → Cloud Run → Gemini Enterprise Agent Platform):Eventarc 會將 CloudEvent 路由至無伺服器 Cloud Run 工作站,封裝 schemas.py、step4_extract_metadata.py 和 step5_attach_aspects.py。工作人員會將 PDF 位元組串流至 Gemini Enterprise Agent Platform,並強制執行 response_schema,確保每個擷取的屬性都符合 DarkDataExtractedMetadata 和 DocumentGovernanceMetadata。
  3. 手術控制層更新 (Cloud Run → Knowledge Catalog):工作人員會解析父項 Fileset Entry,並執行 catalog_client.update_entry() (含 update_mask={"paths": ["aspects"]} 和明確的 aspect_keys)。即使有數十份文件同時完成處理,每項修改作業也只會更新指定方面的鍵。
  4. 信心度設限管理員隔離:如果掃描舊版 PDF 產生的擷取結果 confidence_score 低於作業門檻 (例如 < 0.85),工作人員會在附加的層面設定 governance_status = "PENDING_STEWARD_REVIEW",讓資料管理員查詢 Knowledge Catalog 中信心度較低的資產,並進行人工審查。

企業如何在正式環境中套用這個模式

企業用途

中繼資料即程式碼的實際運作方式

業務和營運影響

自動化法規稽核與召回準備
(製造業、硬體和能源)

法規遵循系統會將非結構化 PDF 安全性層面 (compliance_classifications、operational_hazard_level) 與結構化 SKU 庫存表 (lakehouse_cross_ref_table) 併入 BigQuery 和 Lakehouse for Apache Iceberg。

當 UL-153 或 CE-LVD 等標準更新時,稽核人員只需幾秒鐘就能找出所有受影響的硬體 SKU 和倉庫位置,不必手動開啟數千個 PDF 資料夾。

以事實為依據的 AI 代理和 RAG 前置篩選
(企業知識助理)

Gemini Enterprise Agent Platform 的檢索增強生成 (RAG) 代理程式會先依據 governance_status = "VERIFIED_PRODUCTION" 和 version_provenance 篩選 Knowledge Catalog 項目,再回答現場工程師的問題。

防止 AI 助理產生幻覺,或引用已淘汰的工程草案、過期的標準作業程序,或未經驗證的第三方手冊。

多團隊並行擴充
(去中心化資料網格)

多個獨立管道會擴充同一個檔案集 Entry:AI 工程人員更新 dark-data-extracted-metadata、法務人員透過 document-governance-aspect 附加保留政策,以及安全人員附加 DLP 敏感度層面。

由於 UpdateEntryRequest 會透過 aspect_keys 變更範圍,因此每個網域團隊都能獨立部署及更新中繼資料管道,不會覆寫其他團隊的治理資料。

透過 GitOps 進行 CI/CD 架構演變
(平台和資料工程)

平台團隊會在 Git 中儲存 schemas.py 以及 Terraform google_dataplex_aspect_type 定義。部署前,提取要求會驗證任何新的 Pydantic 欄位在 AspectType 範本中是否具有相符的 1 基礎 index。

消除應用程式擷取程式碼與 Knowledge Catalog 控制層之間的結構定義差異,適用於 dev、staging 和 prod 專案。

11. 清理環境

在 Knowledge Catalog 中,如果子項項目仍參照父項容器和結構定義範本,就無法刪除這些項目。cleanup.py 會以嚴格的反向依附元件順序刪除資源 (Entry → EntryGroup → EntryType → AspectTypes → Cloud Storage bucket 物件 → 暫時 JSON 檔案):

# 1. Delete Fileset Entry first
catalog_client.delete_entry(name=target_entry_path)

# 2. Delete EntryGroup container
del_eg_op = catalog_client.delete_entry_group(name=target_eg_path)
if hasattr(del_eg_op, "result"):
    del_eg_op.result()

# 3. Delete EntryType and Custom AspectTypes
del_et_op = catalog_client.delete_entry_type(name=target_et_path)
if hasattr(del_et_op, "result"):
    del_et_op.result()
for aspect_id_val in (EXTRACTED_ASPECT_TYPE_ID, GOVERNANCE_ASPECT_TYPE_ID):
    target_at_path = f"{active_parent}/aspectTypes/{aspect_id_val}"
    del_at_op = catalog_client.delete_aspect_type(name=target_at_path)
    if hasattr(del_at_op, "result"):
        del_at_op.result()

執行「cleanup.py」

python3 cleanup.py
Starting reverse-dependency resource cleanup in Knowledge Catalog...
Deleted Fileset Entry:
  projects/your-project-id/locations/us-central1/entryGroups/
  dark-data-fileset-group/entries/sop-manuals-fileset-lum8g8j
Deleted EntryGroup   :
  projects/your-project-id/locations/us-central1/entryGroups/
  dark-data-fileset-group
Deleted EntryType    :
  projects/your-project-id/locations/us-central1/entryTypes/
  unstructured-pdf-fileset
Deleted AspectType   :
  projects/your-project-id/locations/us-central1/aspectTypes/
  dark-data-extracted-metadata
Deleted AspectType   :
  projects/your-project-id/locations/us-central1/aspectTypes/
  document-governance-aspect
Deleted Cloud Storage object :
  gs://your-project-id-dark-data-manuals/sop-pdfs/LUM-LIG-DES-8G8J_manual.pdf
Deleted Cloud Storage bucket : gs://your-project-id-dark-data-manuals
Removed local temporary file : extracted_metadata.json
Removed local temporary file : governance_metadata.json
✓ Standalone teardown complete. Environment cleanly reset.

移除複製的存放區目錄

cleanup.py 刪除雲端資源後,請返回主目錄,然後從 Cloud Shell 移除複製的 devrel-demos 存放區資料夾:

cd ~
rm -rf ~/devrel-demos

12. 恭喜!

您已建構並驗證端對端中繼資料即程式碼治理管道,可使用 Gemini Enterprise Agent Platform,將 Cloud Storage 中的非結構化 PDF 轉換為可探索且強制執行結構定義的 Knowledge Catalog 資產。

後續步驟

  • 透過 Terraform 自動化管理:在 CI/CD 管道中,以宣告方式管理 google_dataplex_aspect_type 和 google_dataplex_entry_group 資源。
  • 部署 Eventarc + Cloud Run Worker:將 step4_extract_metadata.py 和 step5_attach_aspects.py 容器化,成為事件驅動的 Cloud Run 服務。
  • 在 BigQuery 中進行下游項目清單聯結:匯出擷取的治理中繼資料 (lakehouse_cross_ref_table),將非結構化文件法規遵循屬性與結構化 SKU 項目清單資料表聯結。

參考文件