Ingestion of Unstructured Documents with Metadata in Vertex AI Search
Source notebook
Repo path:
search/vais-building-blocks/ingesting_unstructured_documents_with_metadata.ipynb· Open on GitHub · intermediate
Ingests PDFs with metadata into Vertex AI Search and queries results with metadata filters.
Summary
This notebook shows how to prepare unstructured PDF documents and metadata for Vertex AI Search ingestion using Discovery Engine REST calls and Cloud Storage. It creates or reuses a datastore and search app, optionally patches a metadata schema, builds JSONL rows that join document URIs with structData, imports the JSONL asynchronously, and runs searches with and without a metadata filter.
Key code patterns
Authenticated REST session
from google.auth import default
from google.auth.transport.requests import AuthorizedSession
creds, _ = default()
authed_session = AuthorizedSession(creds)The notebook uses Application Default Credentials and AuthorizedSession for Discovery Engine REST calls.
Create chunked datastore
payload = {
"displayName": datastore_id,
"solutionTypes": ["SOLUTION_TYPE_SEARCH"],
"contentConfig": "CONTENT_REQUIRED",
"documentProcessingConfig": {
"chunkingConfig": {"layoutBasedChunkingConfig": {
"chunkSize": 500,
"includeAncestorHeadings": True}},
"defaultParsingConfig": {"layoutParsingConfig": {}}
}
}Chunk mode, layout parsing, and ancestor headings are chosen in the notebook to optimize accuracy.
Create search app
payload = {
"displayName": app_id,
"dataStoreIds": [datastore_id],
"solutionType": "SOLUTION_TYPE_SEARCH",
"searchEngineConfig": {
"searchTier": "SEARCH_TIER_ENTERPRISE",
"searchAddOns": ["SEARCH_ADD_ON_LLM"]
}
}The app connects the datastore to an enterprise search engine with the LLM add-on.
Patch metadata schema
schema = {"structSchema": {"type": "object", "properties": {
"doc_name": {"keyPropertyMapping": "title", "retrievable": True, "type": "string"},
"quarter": {"retrievable": True, "indexable": True, "searchable": False, "type": "string"},
"stock_tickers": {"type": "array", "items": {"type": "string", "keyPropertyMapping": "category"}}
}}}
authed_session.patch(schema_url, json=schema)The schema controls which metadata fields are retrievable, searchable, indexable, and specially mapped.
Build JSONL import rows
def prepare_jsonl(row):
return {
"id": row[FIELD_FOR_FILE_NAME],
"structData": row.to_dict(),
"content": {
"mimeType": "application/pdf",
"uri": f"{GCS_DIRECTORY_DOCS}{row[FIELD_FOR_FILE_NAME]}.pdf"
}
}Each JSONL record joins metadata with the GCS URI and MIME type for the unstructured document.
Import from GCS JSONL
payload = {
"reconciliationMode": "INCREMENTAL",
"gcsSource": {"inputUris": [gcs_uri]}
}
response = authed_session.post(
f"{datastore_url}/branches/default_branch/documents:import",
data=json.dumps(payload), headers={"Content-Type": "application/json"})
import_lro = response.json()["name"]Document import is asynchronous and returns a long-running operation name to poll.
Metadata-filtered search
response = authed_session.post(search_url, json={
"query": "Google revenue",
"filter": "quarter: ANY(\"Q2\")"
})The notebook demonstrates narrowing search results to documents whose metadata matches Q2.
Models & APIs used
- APIs / services: Service Usage API, Cloud Storage API, Discovery Engine API, Vertex AI Search
- SDKs / libraries:
google-cloud-storage,google-auth,pandas,requests
When to use this
Use this pattern when ingesting unstructured documents from Cloud Storage into Vertex AI Search with metadata for retrieval, filtering, boosting, or returned context.
Gotchas & caveats
- Billing and the Service Usage API, Cloud Storage API, and Discovery Engine API must be enabled.
- The notebook expects project access through Colab authentication or Application Default Credentials.
- Permissions listed include service usage admin, service account admin, discovery engine admin, and storage object admin roles.
- Datastore location is chosen at creation and must be used when querying; the notebook offers global, us, and eu.
- Datastore creation, app creation, schema update, and document import may take minutes and are polled as long-running operations.
- The sample metadata generator assumes filenames matching patterns like 2022_Q1_Earnings_Transcript.pdf.
- The JSONL join assumes FIELD_FOR_FILE_NAME metadata values match document filename stems.
- Outside Colab, packages such as google.cloud.storage and google.auth may need installation.
Best practices
- Use layout-based chunking with a 500 token chunk size and ancestor headings when accuracy on complex documents matters.
- Provide an explicit metadata schema when fields must be retrievable, indexable, searchable, filterable, or specially mapped.
- Represent each document as JSONL with id, structData, content.mimeType, and content.uri.
- Poll long-running operations for schema updates and document imports before relying on the datastore.
- Keep documents, metadata, and generated JSONL in the same temporary bucket when cleanup simplicity matters.
- Use metadata filters such as quarter: ANY(“Q2”) to constrain search results to matching documents.
Related
- Concepts: Getting Started · Vertex AI Search · RAG & Grounding
- Entities: Cloud Storage
- Area: Vertex AI Search Notebooks
- Best practices: Getting Started - Best Practices · Vertex AI Search - Best Practices · RAG & Grounding - Best Practices