<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vo Tu Duc</title>
    <description>The latest articles on DEV Community by Vo Tu Duc (@votuduc).</description>
    <link>https://dev.to/votuduc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2608391%2F3d1f97c0-f0aa-4280-9419-6406d241fe9a.png</url>
      <title>DEV Community: Vo Tu Duc</title>
      <link>https://dev.to/votuduc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/votuduc"/>
    <language>en</language>
    <item>
      <title>Production RAG on the Lakehouse with BigQuery Vector Search and Apache Iceberg</title>
      <dc:creator>Vo Tu Duc</dc:creator>
      <pubDate>Wed, 23 Sep 2026 04:24:08 +0000</pubDate>
      <link>https://dev.to/gde/production-rag-on-the-lakehouse-with-bigquery-vector-search-and-apache-iceberg-5g3</link>
      <guid>https://dev.to/gde/production-rag-on-the-lakehouse-with-bigquery-vector-search-and-apache-iceberg-5g3</guid>
      <description>&lt;p&gt;As teams rush to build with Generative AI, they're creating a dangerous chasm between their data and AI stacks. This common architectural flaw introduces massive technical debt and business risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: The Great Divide Between Data and AI Stacks
&lt;/h2&gt;

&lt;p&gt;The explosion of Generative AI and &lt;a href="https://votuduc.com/building-a-rag-context-manager-with-apps-script-and-gemini-pro-p-20260505625174" rel="noopener noreferrer"&gt;Building a RAG Context Manager with Apps Script and Gemini Pro&lt;/a&gt; (RAG) has unlocked incredible potential, but it has also exposed a fundamental architectural flaw in how many organizations are building these systems. As teams rush to production, they often inadvertently create a deep chasm between their core data infrastructure and their new AI stack. This divide isn't just an inconvenience; it's a source of significant technical debt, operational complexity, and business risk.&lt;br&gt;
At the heart of the issue is the separation of concerns gone awry. The data lives in one universe—the data lakehouse, governed by decades of best practices in security, governance, and reliability. The AI, particularly the vector search component, lives in another—a specialized, often external, database. Bridging this gap requires brittle pipelines, data duplication, and fragmented security models, ultimately undermining the very reliability and trustworthiness we seek to build into our AI applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhtgf2qgo30qgj5sbucdz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhtgf2qgo30qgj5sbucdz.png" alt="The Traditional, Fragmented RAG Architecture" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Traditional Vector Databases Create Data Silos
&lt;/h3&gt;

&lt;p&gt;The conventional approach to building a RAG system follows a familiar, yet problematic, pattern. You begin with your curated, high-quality data residing in a centralized platform like a data lakehouse. This is your source of truth. To make this data accessible to a Large Language Model (LLM), you must:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Extract:&lt;/strong&gt; Pull the data out of your source-of-truth system (e.g., an Apache Iceberg table in your lakehouse).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Transform:&lt;/strong&gt; Chunk the text into manageable pieces and generate vector embeddings for each chunk using a model.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Load:&lt;/strong&gt; Push both the vector embeddings and the associated text/metadata into a separate, standalone vector database (e.g., Pinecone, Milvus, Weaviate).
This ETL-like process effectively creates a &lt;em&gt;read replica&lt;/em&gt; of your original data, but one that is optimized for vector similarity search. While this works for a proof-of-concept, it's a recipe for disaster in production. You've just created a new data silo.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This new silo is completely disconnected from the original data's lifecycle. It has its own infrastructure to manage, its own APIs to learn, and its own failure modes to handle. More importantly, it requires a complex and often fragile synchronization process to keep it from becoming stale. Every time data is updated, deleted, or added in the source system, a corresponding change must be perfectly orchestrated and propagated to the vector database. This adds immense operational overhead and introduces a new, critical point of failure in your AI stack.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Challenge of Metadata Drift and Security Fragmentation
&lt;/h3&gt;

&lt;p&gt;The consequences of this data silo extend far beyond mere operational complexity. Two critical challenges emerge that directly impact the quality and security of your AI application: metadata drift and security fragmentation.&lt;br&gt;
&lt;strong&gt;Metadata Drift&lt;/strong&gt; is the silent killer of RAG system accuracy. It occurs when the data in your source-of-truth lakehouse changes, but those changes aren't immediately and atomically reflected in the vector database.&lt;br&gt;
Consider a product catalog table in your lakehouse. A product's price is updated, or its status changes to "recalled." If your synchronization pipeline fails or runs on a delay, your RAG-powered chatbot could retrieve the old, stale context from the vector database and confidently provide a customer with an incorrect price or, worse, recommend a recalled product. This isn't just a technical glitch; it's a direct erosion of user trust and a potential business liability. The vector index has "drifted" from the ground truth, and your RAG system is now hallucinating based on outdated facts.&lt;br&gt;
&lt;strong&gt;Security Fragmentation&lt;/strong&gt; presents an equally severe governance and compliance risk. Your enterprise data lakehouse is built upon a robust, unified security model. You have fine-grained controls—IAM roles, row-level access policies, and column-level security—that dictate precisely who can see what data.&lt;br&gt;
When you copy that data into a separate vector database, you are forced to reimplement that entire security model from scratch in a new environment. This is not only a duplication of effort but also a massive security risk. It's incredibly difficult to keep two disparate security models perfectly in sync. An employee who leaves the company might have their access revoked in the lakehouse, but their access to the sensitive data copied in the vector store might persist. This fragmentation creates security gaps, doubles the administrative burden, and makes compliance audits a nightmare.&lt;/p&gt;
&lt;h3&gt;
  
  
  Introducing the Lakehouse as the Single Source of Truth for Enterprise AI
&lt;/h3&gt;

&lt;p&gt;What if we could eliminate the divide? What if, instead of moving the data to a separate AI system, we brought the AI capabilities directly to the data? This is the foundational principle of building production-grade AI on the Lakehouse.&lt;br&gt;
The modern data lakehouse, combining the scalability of a data lake with the performance and transactional integrity of a data warehouse, is already the established single source of truth for enterprise analytics. It houses your most valuable, curated, and governed data assets. By integrating vector search as a native feature within this platform—as BigQuery has done—we can fundamentally change the architectural paradigm.&lt;br&gt;
In this model, vector embeddings are not shipped to an external system; they become just another data type, a new column (&lt;code&gt;ARRAY&amp;amp;lt;FLOAT64&amp;amp;gt;&lt;/code&gt;) in your existing Apache Iceberg or BigQuery native tables. The vector index is built directly on top of this column, co-located with the source data it represents.&lt;br&gt;
This elegant simplification solves our critical challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;No More Silos:&lt;/strong&gt; Data never leaves the lakehouse. There is no duplication, no ETL to a separate vector store, and no synchronization pipelines to maintain.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Zero Data Drift:&lt;/strong&gt; When you update a row in your Iceberg table, the change is atomic. The text, the metadata, and the vector embedding are all updated together in a single transaction. The vector index is always perfectly consistent with the source of truth because it &lt;em&gt;is&lt;/em&gt; the source of truth.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Unified Security and Governance:&lt;/strong&gt; The same robust security model that protects your entire data estate automatically applies to your vector embeddings and search queries. The row-level permissions, column-level security, and IAM policies you've already defined are inherited seamlessly. There is one security model to manage, not two.
By treating vector search as a first-class workload within the data lakehouse, we move from a brittle, fragmented architecture to a unified, robust, and secure platform for enterprise AI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkwfrw4d1s27o0bcl24z6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkwfrw4d1s27o0bcl24z6.png" alt=" The Unified Production RAG Blueprint" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Architectural Blueprint: A Unified RAG Pipeline on Google Cloud
&lt;/h2&gt;

&lt;p&gt;To build a robust, production-grade RAG system on the lakehouse, we need more than just a collection of services; we need a cohesive architecture where each component plays a specific, complementary role. Our blueprint unifies data management, machine learning, and analytics within a single, governable ecosystem on Google Cloud. This approach moves beyond siloed vector databases, bringing AI capabilities directly to your data's center of gravity—the data lakehouse. The result is a streamlined, scalable, and cost-effective pipeline that transforms raw information into intelligent, contextual responses.&lt;/p&gt;
&lt;h3&gt;
  
  
  Core Components: BigQuery, &lt;a href="https://votuduc.com/building-self-correcting-agentic-workflows-with-vertex-ai-p-20260505760079" rel="noopener noreferrer"&gt;Building Self-Correcting Agentic Workflows  with Vertex AI&lt;/a&gt;, Apache Iceberg, and Cloud Storage.
&lt;/h3&gt;

&lt;p&gt;The power of this architecture lies in the synergy between four key Google Cloud and open-source technologies. Let's break down the role of each player.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Google Cloud Storage (GCS):&lt;/strong&gt; This is the foundational layer of our lakehouse. GCS acts as the scalable, durable, and cost-effective landing zone for all our raw, unstructured source data—PDFs, Word documents, Markdown files, transcripts, and more. It is the "lake" where our data assets reside in their native format before being processed.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Apache Iceberg:&lt;/strong&gt; This is the star of our data management strategy. Iceberg is not just a file format; it's an open table format that brings the reliability and structure of a traditional database directly to the vast data lake on GCS. In our RAG pipeline, Iceberg is critical for several reasons:&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Transactional Integrity:&lt;/strong&gt; It provides ACID-like transactional guarantees for our embeddings and metadata, preventing data corruption during concurrent writes.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Schema Evolution:&lt;/strong&gt; It allows us to evolve our data schema (e.g., adding new metadata fields) without rewriting the entire dataset.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Performance:&lt;/strong&gt; Features like partition evolution and file pruning optimize query performance, which is crucial when dealing with billions of vectors.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Openness:&lt;/strong&gt; As an open standard, it prevents vendor lock-in and ensures our core data assets are portable and accessible by various engines like Spark, Flink, and, most importantly for us, BigQuery.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Vertex AI:&lt;/strong&gt; This is our intelligence engine, providing the state-of-the-art models needed to understand and represent our text data. Specifically, we leverage the &lt;strong&gt;Vertex AI Embedding APIs&lt;/strong&gt; (e.g., &lt;code&gt;text-embedding-004&lt;/code&gt;). These managed, scalable endpoints take our processed text chunks as input and convert them into high-dimensional numerical vectors (embeddings). This process is the heart of the "retrieval" mechanism, as it encodes the semantic meaning of our text into a format that machines can compare for similarity.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;BigQuery:&lt;/strong&gt; BigQuery is the central nervous system that unifies the entire architecture. It has evolved far beyond a traditional data warehouse and serves two primary functions in our pipeline:&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Lakehouse Query Engine:&lt;/strong&gt; Through its &lt;strong&gt;BigLake&lt;/strong&gt; capabilities, BigQuery can directly read from and query the Apache Iceberg tables stored on GCS. This allows us to use familiar SQL to manage, inspect, and analyze our text chunks and their corresponding embeddings without moving the data.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Vector Search Engine:&lt;/strong&gt; This is the game-changer. BigQuery has native &lt;strong&gt;Vector Search&lt;/strong&gt; functionality. It can build and manage a highly efficient Approximate Nearest Neighbor (ANN) index directly on the embedding column within our Iceberg table. This eliminates the need for a separate, dedicated vector database, consolidating our entire RAG backend into a single, powerful platform.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Data Flow: From Unstructured Data to Indexed Embeddings in Iceberg
&lt;/h3&gt;

&lt;p&gt;The process of converting raw documents into a searchable vector index follows a clear, automated data pipeline. This is the "indexing" half of the RAG workflow.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Ingestion:&lt;/strong&gt; The pipeline begins when new unstructured documents (e.g., &lt;code&gt;annual-report-2023.pdf&lt;/code&gt;) are uploaded to a designated GCS bucket.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Parsing and Chunking:&lt;/strong&gt; An event-driven process, such as a Cloud Function or a more robust Dataflow job, is triggered by the new file. This process is responsible for:&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Parsing:&lt;/strong&gt; Extracting the raw text content from the source file.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Chunking:&lt;/strong&gt; Strategically splitting the extracted text into smaller, semantically coherent chunks. The chunking strategy (e.g., fixed size, recursive character splitting, etc.) is a critical factor in the quality of the retrieval results.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Embedding Generation:&lt;/strong&gt; For each text chunk, the processing job makes an API call to a Vertex AI Embedding model endpoint. The API responds with a high-dimensional vector (e.g., a 768-dimension array of floating-point numbers) that captures the semantic essence of that chunk.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Writing to Iceberg:&lt;/strong&gt; The original text chunk, its newly generated vector embedding, and any relevant metadata (e.g., source document name, page number, chunk ID) are packaged together. This structured record is then appended to our primary Apache Iceberg table residing on GCS. Thanks to Iceberg's transactional nature, this write operation is atomic and safe.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Vector Indexing:&lt;/strong&gt; After the data is written to the Iceberg table, we use a simple BigQuery DDL statement to create or update a &lt;code&gt;VECTOR_INDEX&lt;/code&gt;. BigQuery automatically handles the complex process of building the ANN index in the background. This index is what enables lightning-fast similarity searches across potentially billions of vectors during the retrieval step.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  The Role of BigQuery Vector Search as the Unifying Engine
&lt;/h3&gt;

&lt;p&gt;The most transformative aspect of this architecture is how BigQuery Vector Search acts as the unifying force, collapsing what were once disparate systems into a single, cohesive plane.&lt;br&gt;
Traditionally, a RAG pipeline required managing at least three separate systems: an object store for raw files (GCS), a dedicated vector database for ANN search (e.g., Pinecone, Weaviate), and a data warehouse for structured metadata and analytics (BigQuery). This separation introduces complexity in data movement (ETL), security, governance, and operational overhead.&lt;br&gt;
Our blueprint eliminates this fragmentation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Data Stays Put:&lt;/strong&gt; By querying Iceberg tables on GCS via BigLake and building the vector index in place, BigQuery brings the compute to the data. There is no need to duplicate and move terabytes of embedding data into a separate database, significantly simplifying the data pipeline and reducing costs.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Unified Governance and Security:&lt;/strong&gt; Your embeddings and metadata are governed by the same robust security model you already use for BigQuery. You can manage access control at the project, dataset, table, and even column level using familiar IAM policies. This is a massive win for enterprise security and compliance.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Powerful Hybrid Search with SQL:&lt;/strong&gt; This is the killer feature. Because the vector embeddings live in the same table as structured metadata, you can perform sophisticated filtered vector searches in a single, elegant SQL query. Consider a query like: "Find the top 5 document chunks most similar to {user_query}, but only from documents published after '2023-01-01' and tagged with 'finance'." In a siloed system, this is a complex multi-step process: query the vector DB, get IDs, then query the data warehouse with those IDs to filter. With BigQuery, it's a single, optimized query, unlocking powerful, context-aware retrieval that is simply not feasible otherwise.
By positioning BigQuery as the central engine for both data management and vector search, we create a truly unified, scalable, and operationally efficient foundation for production-grade RAG on the lakehouse.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Step 1 Preparing and Embedding Your Enterprise Data
&lt;/h2&gt;

&lt;p&gt;The foundation of any high-performing RAG system isn't the LLM—it's the data. The quality, structure, and semantic representation of your knowledge base directly dictate the relevance and accuracy of the generated responses. In a Lakehouse architecture, this first step is about establishing a robust, scalable, and open foundation for your data and then transforming it into a format that machine learning models can understand: high-dimensional vectors.&lt;br&gt;
We'll tackle this by first defining our data's home using Apache Iceberg tables in BigQuery, and then processing our raw documents into vectorized "chunks" using Vertex AI's powerful embedding models.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting Up Apache Iceberg Tables in BigQuery
&lt;/h3&gt;

&lt;p&gt;Before we can ingest anything, we need a destination. Why Apache Iceberg? In the context of a Lakehouse, Iceberg provides critical features that traditional data warehousing tables lack. It's an open table format that decouples the table structure from the physical storage (in our case, Google Cloud Storage), offering schema evolution, time travel, and efficient file-level operations. This makes it perfect for managing large, evolving datasets of document chunks and their corresponding embeddings.&lt;br&gt;
We'll create a BigQuery "BigLake" table backed by Iceberg. This table will serve as our "vector store" source of truth, holding the original text chunks, their vector embeddings, and any relevant metadata.&lt;/p&gt;

&lt;p&gt;Here’s the DDL to create our core table, &lt;code&gt;doc_embeddings_iceberg&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;your_dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doc_embeddings_iceberg&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;chunk_id&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;OPTIONS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"Unique identifier for the text chunk"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;doc_source&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt; &lt;span class="k"&gt;OPTIONS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"Identifier for the original source document, e.g., GCS path"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;chunk_text&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt; &lt;span class="k"&gt;OPTIONS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"The actual text content of the chunk"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;ARRAY&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FLOAT64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;OPTIONS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;"The 768-dimension vector embedding from Vertex AI"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;OPTIONS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ICEBERG'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;table_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;uris&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'gs://your-gcs-bucket/iceberg-warehouse/doc_embeddings'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="n"&gt;connector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'biglake-connector-v1'&lt;/span&gt; &lt;span class="c1"&gt;-- Ensure your BigLake connection is set up&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down the key components of this schema:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;chunk_id&lt;/code&gt;&lt;/strong&gt;: A unique primary key for each piece of text. This is crucial for referencing and updating specific chunks. A UUID or a hash of the content works well.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;doc_source&lt;/code&gt;&lt;/strong&gt;: Links the chunk back to its parent document. This is vital for providing citations and context in the final RAG output.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;chunk_text&lt;/code&gt;&lt;/strong&gt;: The raw text that was vectorized. We store this so we can retrieve the actual content to feed into the LLM's context window.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;embedding&lt;/code&gt;&lt;/strong&gt;: The star of the show. This &lt;code&gt;ARRAY&amp;lt;FLOAT64&amp;gt;&lt;/code&gt; column will hold the numerical vector generated by our embedding model.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;OPTIONS&lt;/code&gt;&lt;/strong&gt;: We explicitly define the format as &lt;code&gt;ICEBERG&lt;/code&gt; and specify the GCS path where the underlying Parquet and metadata files will be stored. This is the core of the Lakehouse pattern—SQL on open files in your data lake.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Using Vertex AI Embedding Models for High-Dimensional Vectors
&lt;/h3&gt;

&lt;p&gt;With our table ready, we need a way to convert text into meaningful vectors. An embedding is a dense vector representation of a piece of data (in our case, text) where semantically similar items are located closer together in the vector space.&lt;br&gt;
Google's Vertex AI offers state-of-the-art embedding models that are managed, scalable, and optimized for various tasks. For our RAG use case, we'll use the &lt;code&gt;text-embedding-004&lt;/code&gt; model, which generates a 768-dimensional vector. Its &lt;code&gt;task_type&lt;/code&gt; parameter is specifically designed to optimize embeddings for retrieval, making it ideal for creating a searchable knowledge base.&lt;br&gt;
Here’s a &lt;a href="https://votuduc.com/JSON-to-Video-Automated-Rendering-Engine-p618510" rel="noopener noreferrer"&gt;JSON-to-Video Automated Rendering Engine&lt;/a&gt; snippet demonstrating how to generate embeddings for a batch of text chunks using the Vertex AI SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;vertexai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vertexai.language_models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TextEmbeddingModel&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;text_chunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt;
&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Generates embeddings for a list of text chunks.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="n"&gt;vertexai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# We use the latest text embedding model, optimized for retrieval
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextEmbeddingModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text-embedding-004&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# The 'task_type' is critical for optimizing vectors for RAG
# 'RETRIEVAL_DOCUMENT' is used for the text being indexed.
# 'RETRIEVAL_QUERY' would be used for the user's input query.
&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;text_chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;task_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RETRIEVAL_DOCUMENT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Extract the numerical vector from the response object
&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# --- Example Usage ---
&lt;/span&gt;&lt;span class="n"&gt;my_project_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gcp-project-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;my_location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us-central1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;my_chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Apache Iceberg is an open table format for huge analytic datasets.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BigQuery vector search enables efficient similarity search on embeddings.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A Lakehouse architecture combines the benefits of data lakes and data warehouses.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;vector_embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_project_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# The output 'vector_embeddings' is a list of lists,
# where each inner list is a 768-dimension vector.
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Generated &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vector_embeddings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; embeddings.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dimension of first embedding: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vector_embeddings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function is the core of our text-to-vector transformation. It takes a list of strings and returns a corresponding list of 768-dimension floating-point vectors, ready to be inserted into our Iceberg table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Batch Ingestion and Structuring Data for Vectorization
&lt;/h3&gt;

&lt;p&gt;Now we connect the pieces. The final step is to create a scalable batch pipeline that reads raw documents, processes them into chunks, generates embeddings, and loads the results into our BigQuery Iceberg table.&lt;br&gt;
&lt;strong&gt;1. Data Sourcing and Chunking&lt;/strong&gt;&lt;br&gt;
Your enterprise data likely lives in various formats (PDFs, DOCX, HTML) and locations (GCS, Confluence, etc.). The first task is to extract the raw text. Once you have the text, you must break it down into smaller, semantically meaningful chunks. This is perhaps the most important tuning parameter in a RAG system.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Why Chunk?&lt;/strong&gt; LLMs have a limited context window, and embedding models work best on focused, concise pieces of text. Sending an entire 100-page document to an embedding model is ineffective.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Chunking Strategy:&lt;/strong&gt; A simple fixed-size chunk (e.g., 500 characters) is a start, but it can awkwardly split sentences or ideas. A better approach is to use a recursive character text splitter, which tries to split on natural boundaries like paragraphs (&lt;code&gt;\n\n&lt;/code&gt;), then sentences (&lt;code&gt;.&lt;/code&gt;), then spaces (&lt;code&gt;&lt;/code&gt;). Adding a small overlap between chunks (e.g., 50 characters) helps preserve context across boundaries.
&lt;strong&gt;2. The Batch Processing Pipeline&lt;/strong&gt;
A production-grade ingestion pipeline can be orchestrated with tools like Cloud Run, Cloud Functions, or Apache Beam on Dataflow. The logic remains the same:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This is a conceptual pipeline structure, not a complete, runnable script.
# You would use libraries like 'google-cloud-bigquery' and 'pypdf'
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.cloud&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;bigquery&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_and_ingest_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents_to_process&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Conceptual pipeline to chunk, embed, and ingest documents.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="n"&gt;all_rows_to_insert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;doc_path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;documents_to_process&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="c1"&gt;# Step 1: Extract text from the source document (e.g., a PDF in GCS)
&lt;/span&gt;&lt;span class="n"&gt;raw_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_text_from_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Your custom text extraction logic
# Step 2: Chunk the text using a chosen strategy
&lt;/span&gt;&lt;span class="n"&gt;text_chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;chunk_text_recursively&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk_overlap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Step 3: Generate embeddings for the chunks in batches
# (API has a limit on items per call)
&lt;/span&gt;&lt;span class="n"&gt;chunk_embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;project_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gcp-project-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us-central1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;text_chunks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;text_chunks&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Step 4: Structure the data for insertion
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text_chunks&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chunk_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;doc_source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;doc_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chunk_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chunk_embeddings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CURRENT_TIMESTAMP()&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# Let BigQuery handle this
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;all_rows_to_insert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Step 5: Batch load the data into the BigQuery Iceberg table
# The BigQuery Python client can handle streaming inserts or batch loads from GCS
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;all_rows_to_insert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bigquery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;table_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_dataset.doc_embeddings_iceberg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="c1"&gt;# For large volumes, loading from a file (JSON, Parquet) in GCS is more robust
# For simplicity, this example uses streaming inserts
&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert_rows_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;all_rows_to_insert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Successfully inserted &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;all_rows_to_insert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Encountered errors while inserting rows: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# --- Example Invocation ---
# In a real pipeline, this list would come from scanning a GCS bucket
&lt;/span&gt;&lt;span class="n"&gt;source_docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gs://my-knowledge-base/doc1.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gs://my-knowledge-base/doc2.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;process_and_ingest_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source_docs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;By executing this pipeline, you systematically convert your unstructured enterprise documents into a structured, vectorized dataset within your Lakehouse. This Iceberg table is now the single source of truth for your knowledge base, ready to be indexed for lightning-fast similarity search in the next step.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Indexing and Searching Directly on Iceberg Tables
&lt;/h2&gt;

&lt;p&gt;With our embeddings now residing in an Apache Iceberg table managed by BigQuery, we can unlock the power of high-performance retrieval without any data movement. This is where the tight integration between the Lakehouse storage format and BigQuery's analytical engine truly shines. We will create a vector index directly on the Iceberg table, enabling low-latency similarity searches that are essential for a responsive RAG application.&lt;/p&gt;
&lt;h3&gt;
  
  
  Creating a Vector Index on Your BigQuery Iceberg Table
&lt;/h3&gt;

&lt;p&gt;A vector index is a specialized data structure that reorganizes your high-dimensional embedding data to enable Approximate Nearest Neighbor (ANN) search. Instead of exhaustively comparing a query vector to every single vector in your table (a brute-force approach), the index allows the system to quickly narrow down the search to a small, promising subset of candidates. This is the key to achieving millisecond-level latency on datasets with millions or even billions of vectors.&lt;br&gt;
In BigQuery, creating a vector index is a straightforward DDL operation. Let's assume our Iceberg table is named &lt;code&gt;rag_documents&lt;/code&gt; and has the following simplified schema:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;doc_id&lt;/code&gt; (STRING, PRIMARY KEY)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chunk_text&lt;/code&gt; (STRING)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;embedding&lt;/code&gt; (ARRAY&amp;lt;FLOAT64&amp;gt;)
You would create an index on the &lt;code&gt;embedding&lt;/code&gt; column using the &lt;code&gt;CREATE VECTOR INDEX&lt;/code&gt; statement.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;VECTOR&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;my_doc_index&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;my_dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rag_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;OPTIONS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;index_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'IVF'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;distance_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'COSINE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;ivf_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'{"num_lists": 500}'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let's break down the &lt;code&gt;OPTIONS&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;index_type = 'IVF'&lt;/code&gt;&lt;/strong&gt;: This specifies the Inverted File Index, a highly efficient and widely used ANN indexing algorithm. IVF works by clustering the vectors into partitions (or lists). During a search, it only inspects the partitions closest to the query vector, dramatically reducing the search space.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;distance_type = 'COSINE'&lt;/code&gt;&lt;/strong&gt;: This defines the metric used to measure similarity. For embeddings generated by modern transformer models (like those from Vertex AI or OpenAI), &lt;code&gt;COSINE&lt;/code&gt; similarity is almost always the correct choice. It measures the angle between two vectors, making it robust to differences in vector magnitude. Other options include &lt;code&gt;EUCLIDEAN&lt;/code&gt; (L2 distance) and &lt;code&gt;DOT_PRODUCT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;ivf_options = '{"num_lists": 500}'&lt;/code&gt;&lt;/strong&gt;: This JSON string configures the IVF index. The most critical parameter is &lt;code&gt;num_lists&lt;/code&gt;, which sets the number of partitions to create.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Choosing &lt;code&gt;num_lists&lt;/code&gt;&lt;/strong&gt;: The optimal value depends on your dataset size. A good starting point is the square root of the number of rows in your table. For a table with 1 million vectors, a value between 100 and 1,000 is reasonable. A higher &lt;code&gt;num_lists&lt;/code&gt; can lead to faster queries but may require tuning the query-time &lt;code&gt;probe_count&lt;/code&gt; (which we'll cover next) to maintain high recall.
Index creation is an asynchronous background job. You can monitor its progress by querying the information schema:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;coverage_percentage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;last_refresh_time&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="n"&gt;my_dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFORMATION_SCHEMA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VECTOR_INDEXES&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
&lt;span class="k"&gt;table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'rag_documents'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A &lt;code&gt;coverage_percentage&lt;/code&gt; of 100 indicates that the index is fully built and ready for use. BigQuery automatically keeps the index updated as new data is inserted into your Iceberg table.&lt;/p&gt;
&lt;h3&gt;
  
  
  Executing Low-Latency Similarity Searches with the VECTOR_SEARCH Function
&lt;/h3&gt;

&lt;p&gt;Once the index is active, you can perform searches using the &lt;code&gt;VECTOR_SEARCH&lt;/code&gt; function. This function is the core of the retrieval step in your RAG pipeline. It takes a query vector and efficiently finds the &lt;code&gt;top_k&lt;/code&gt; most similar vectors from your indexed table.&lt;br&gt;
The basic syntax is:&lt;br&gt;
&lt;code&gt;VECTOR_SEARCH(TABLE table_name, column_to_search, query_vector, top_k =&amp;gt; k, options =&amp;gt; '...')&lt;/code&gt;&lt;br&gt;
Here is a practical example. Imagine your application has generated an embedding for the user's question, "What are the latest query optimization techniques?". You would use that embedding to find the most relevant document chunks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Assume @query_embedding is a query parameter passed from your application&lt;/span&gt;
&lt;span class="c1"&gt;-- For this example, we'll use a placeholder array.&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="n"&gt;query_embedding&lt;/span&gt; &lt;span class="n"&gt;ARRAY&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FLOAT64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;query_embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...];&lt;/span&gt; &lt;span class="c1"&gt;-- Your 768 or 1536-dimension query vector&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doc_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chunk_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;search_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="n"&gt;VECTOR_SEARCH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;my_dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rag_documents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- The table with the index&lt;/span&gt;
&lt;span class="s1"&gt;'embedding'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                             &lt;span class="c1"&gt;-- The indexed column&lt;/span&gt;
&lt;span class="n"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                         &lt;span class="c1"&gt;-- The vector to search for&lt;/span&gt;
&lt;span class="n"&gt;top_k&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                             &lt;span class="c1"&gt;-- Number of results to return&lt;/span&gt;
&lt;span class="k"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'{"probe_count": 20}'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;search_results&lt;/span&gt;
&lt;span class="c1"&gt;-- Join back to the base table to retrieve the actual text content&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;
&lt;span class="n"&gt;my_dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rag_documents&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt;
&lt;span class="n"&gt;search_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doc_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doc_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="n"&gt;search_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;-- COSINE distance is 0 for identical, 2 for opposite&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Points:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;The &lt;code&gt;options&lt;/code&gt; Parameter&lt;/strong&gt;: The &lt;code&gt;probe_count&lt;/code&gt; option is the most important performance tuning knob at query time. It tells the IVF index how many partitions (or lists) to inspect during the search. A higher &lt;code&gt;probe_count&lt;/code&gt; increases the chance of finding the true nearest neighbors (higher recall) at the cost of slightly higher latency. A good starting value is &lt;code&gt;sqrt(num_lists)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The &lt;code&gt;JOIN&lt;/code&gt; Pattern&lt;/strong&gt;: &lt;code&gt;VECTOR_SEARCH&lt;/code&gt; returns the primary key columns of your table (&lt;code&gt;doc_id&lt;/code&gt; in this case) and the &lt;code&gt;distance&lt;/code&gt; for each match. You must &lt;code&gt;JOIN&lt;/code&gt; these results back to your base table to retrieve other columns like &lt;code&gt;chunk_text&lt;/code&gt;, which you'll need to pass to the Large Language Model.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Query Optimization for Production Workloads
&lt;/h3&gt;

&lt;p&gt;For a production RAG system, performance and accuracy are paramount. Simply running a basic vector search is often not enough. You need to consider filtering and tuning to ensure your application is both fast and relevant.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pre-filtering vs. Post-filtering
&lt;/h4&gt;

&lt;p&gt;A common requirement in RAG is to search only within a subset of documents. For example, you might want to find information relevant only to a specific user, product, or date range. There are two ways to apply these filters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Post-filtering (Inefficient)&lt;/strong&gt;: You run the vector search on the entire table and then apply a &lt;code&gt;WHERE&lt;/code&gt; clause to the final result set. This is highly inefficient because the vector search wastes resources finding top matches that are immediately discarded by the filter.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Pre-filtering (Efficient)&lt;/strong&gt;: You apply the filter &lt;em&gt;before&lt;/em&gt; the vector search. BigQuery's engine is smart enough to push these predicates down, meaning the vector search only operates on the subset of data that matches your filter. This dramatically reduces the search space, lowers latency, and reduces cost.
To implement pre-filtering, apply the &lt;code&gt;WHERE&lt;/code&gt; clause to the base table within the &lt;code&gt;VECTOR_SEARCH&lt;/code&gt; function itself.
Let's add a &lt;code&gt;source_year&lt;/code&gt; column to our table and compare the two approaches.
&lt;strong&gt;Inefficient Post-filtering:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- AVOID THIS PATTERN&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doc_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chunk_text&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="n"&gt;VECTOR_SEARCH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;my_dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rag_documents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'embedding'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;
&lt;span class="n"&gt;my_dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rag_documents&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doc_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doc_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_year&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2022&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;-- Filter is applied AFTER the expensive search&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Efficient Pre-filtering:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- USE THIS PATTERN&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doc_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chunk_text&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="n"&gt;VECTOR_SEARCH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="c1"&gt;-- The filter is applied to a subquery on the base table&lt;/span&gt;
&lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;my_dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rag_documents&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;source_year&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2022&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="s1"&gt;'embedding'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;top_k&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;
&lt;span class="n"&gt;my_dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rag_documents&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doc_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doc_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By filtering the table &lt;em&gt;before&lt;/em&gt; it's passed to &lt;code&gt;VECTOR_SEARCH&lt;/code&gt;, you ensure the ANN search is performed only on the relevant slice of your data, leading to significant performance gains in production workloads. This is a critical optimization for building scalable, multi-tenant RAG applications on the Lakehouse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 Grounding the Conversational Agent
&lt;/h2&gt;

&lt;p&gt;With a robust retrieval mechanism in place, the next critical step is to use the retrieved information to generate a coherent, accurate, and contextually relevant answer. This is the "Generation" part of Retrieval-Augmented Generation (RAG). It involves skillfully weaving the search results from BigQuery into a prompt that instructs a Large Language Model (LLM) on how to synthesize a final response. This process transforms raw, retrieved data into a conversational and helpful answer, ensuring the model's output is grounded in the facts contained within our Iceberg table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrating Vector Search Results into a Language Model Prompt
&lt;/h3&gt;

&lt;p&gt;The core of grounding lies in &lt;a href="https://votuduc.com/prompt-engineering-for-reliable-autonomous-workspace-agents-p-20260504436320" rel="noopener noreferrer"&gt;Prompt Engineering for Reliable Autonomous Workspace Agents&lt;/a&gt; for &lt;a href="https://votuduc.com/prompt-engineering-for-reliable-autonomous-workspace-agents-p-20260319404106" rel="noopener noreferrer"&gt;Reliable Autonomous Workspace Agents&lt;/a&gt;. We are not simply asking the LLM a question; we are providing it with a specific set of instructions and the exact context it must use to formulate its answer. A well-structured RAG prompt is the key to minimizing hallucinations and ensuring factual consistency.&lt;/p&gt;

&lt;p&gt;A typical RAG prompt consists of three main components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;System Instructions:&lt;/strong&gt; This is the preamble that defines the LLM's persona, its task, and its constraints. It's where you enforce the rule that the model &lt;em&gt;must&lt;/em&gt; base its answer only on the provided context. This is your primary defense against the model reverting to its parametric knowledge and making things up.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Retrieved Context:&lt;/strong&gt; This is the payload from our BigQuery &lt;code&gt;VECTOR_SEARCH&lt;/code&gt; query. We take the text from the top-k retrieved document chunks and concatenate them into a single block of text. It's good practice to clearly delineate each document chunk, for instance, by numbering them or separating them with a distinct marker.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The User's Question:&lt;/strong&gt; The final part of the prompt is the original query from the user.
By combining these elements, we create a single, comprehensive prompt that gives the LLM everything it needs to generate a grounded response.
Here is a template illustrating this structure. Notice how we use placeholders like &lt;code&gt;&amp;amp;#123;context&amp;amp;#125;&lt;/code&gt; and &lt;code&gt;&amp;amp;#123;question&amp;amp;#125;&lt;/code&gt; which our application logic will replace with the actual data at runtime.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are an expert Q&amp;amp;A system that is a world-class expert on internal company documentation.
Your instructions are:
1. Answer the user's QUESTION based ONLY on the provided CONTEXT.
2. Do not use any prior knowledge or information outside of the CONTEXT.
3. If the CONTEXT does not contain the answer, you MUST state that you cannot answer the question with the information provided.
4. Synthesize the information from the CONTEXT into a clear and concise answer. Do not simply copy and paste sections.
5. If the CONTEXT includes source URIs, cite the relevant sources in your answer.
---
CONTEXT:
&amp;amp;#123;context&amp;amp;#125;
---
QUESTION:
&amp;amp;#123;question&amp;amp;#125;
Final Answer:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Building the RAG Logic to Synthesize Answers from Retrieved Context
&lt;/h3&gt;

&lt;p&gt;The orchestration logic is the glue that connects our BigQuery vector index to the LLM. This logic, typically implemented in an application backend (e.g., a Python service running on Cloud Run or a Cloud Function), executes a precise sequence of operations for every incoming user query.&lt;br&gt;
The end-to-end flow is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Receive Query:&lt;/strong&gt; The application receives a question from the end-user.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Embed Query:&lt;/strong&gt; The raw question string is passed to the same text embedding model (e.g., &lt;code&gt;textembedding-gecko@003&lt;/code&gt;) that was used to embed the documents in our Iceberg table. This generates a query vector.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Execute Vector Search:&lt;/strong&gt; The application constructs and executes a &lt;code&gt;VECTOR_SEARCH&lt;/code&gt; query against BigQuery, passing the query vector as a parameter. It retrieves the &lt;code&gt;base_document&lt;/code&gt; (the original text chunk) and any other relevant metadata for the top-k most similar documents.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Format Context:&lt;/strong&gt; The retrieved &lt;code&gt;base_document&lt;/code&gt; texts are collected and formatted into a single string. For example, they can be joined together with a separator like &lt;code&gt;\n---\n&lt;/code&gt;. This string will replace the &lt;code&gt;&amp;amp;#123;context&amp;amp;#125;&lt;/code&gt; placeholder in our prompt template.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Construct Final Prompt:&lt;/strong&gt; The formatted context string and the original user question are injected into the predefined prompt template.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Invoke LLM:&lt;/strong&gt; The complete, final prompt is sent to a generative model API, such as Vertex AI's Gemini 1.0 Pro (&lt;code&gt;gemini-1.0-pro&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Return Response:&lt;/strong&gt; The LLM processes the prompt and generates a response based on the provided context. This response is then returned to the user, completing the RAG cycle.
This sequence ensures that every answer is freshly generated based on the most relevant documents available in the Lakehouse at that moment.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Example Implementation of a Question-Answering Pipeline
&lt;/h3&gt;

&lt;p&gt;Let's translate the logic above into a practical Python implementation. This example uses the &lt;code&gt;google-cloud-bigquery&lt;/code&gt; and &lt;code&gt;vertexai&lt;/code&gt; client libraries to orchestrate the entire pipeline. This function encapsulates the full RAG process: embedding the query, searching BigQuery, and generating the final answer with Gemini.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;vertexai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vertexai.language_models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TextEmbeddingModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TextGenerationModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.cloud&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;bigquery&lt;/span&gt;
&lt;span class="c1"&gt;# --- Configuration ---
&lt;/span&gt;&lt;span class="n"&gt;PROJECT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-gcp-project-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;LOCATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BQ_DATASET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rag_dataset&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BQ_TABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iceberg_docs_embedded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;EMBEDDING_MODEL_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;textembedding-gecko@003&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;GENERATION_MODEL_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-1.0-pro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# Or your preferred Gemini model
# --- Initialize clients ---
&lt;/span&gt;&lt;span class="n"&gt;vertexai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;LOCATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;bq_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bigquery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;embedding_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextEmbeddingModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EMBEDDING_MODEL_NAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# It's best practice to initialize the model once
&lt;/span&gt;&lt;span class="n"&gt;generation_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextGenerationModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GENERATION_MODEL_NAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;PROMPT_TEMPLATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
You are an expert Q&amp;amp;A system that is a world-class expert on internal company documentation.
Your instructions are:
1. Answer the user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s QUESTION based ONLY on the provided CONTEXT.
2. Do not use any prior knowledge or information outside of the CONTEXT.
3. If the CONTEXT does not contain the answer, you MUST state that you cannot answer the question with the information provided.
4. Synthesize the information from the CONTEXT into a clear and concise answer.
---
CONTEXT:
{context}
---
QUESTION:
{question}
Final Answer:
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_rag_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Orchestrates the RAG pipeline:
1. Embeds the user question.
2. Searches BigQuery for relevant documents.
3. Generates a response using an LLM.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="c1"&gt;# 1. Embed the user's question
&lt;/span&gt;&lt;span class="n"&gt;question_embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embedding_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_embeddings&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;])[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;
&lt;span class="c1"&gt;# 2. Execute VECTOR_SEARCH in BigQuery
&lt;/span&gt;&lt;span class="n"&gt;sql_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
SELECT
base_document,
distance
FROM
VECTOR_SEARCH(
TABLE `&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BQ_DATASET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BQ_TABLE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;`,
&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,
(SELECT &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question_embedding&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; AS embedding),
top_k =&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,
distance_type =&amp;gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;COSINE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;
)
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="n"&gt;query_job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bq_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql_query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# 3. Format the retrieved context
&lt;/span&gt;&lt;span class="n"&gt;context_chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_document&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;context_chunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I could not find any relevant information to answer your question.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;context_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;---&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context_chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 4. Construct the final prompt
&lt;/span&gt;&lt;span class="n"&gt;final_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PROMPT_TEMPLATE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context_string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 5. Invoke the LLM to generate the final answer
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generation_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;final_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;max_output_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;top_p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="c1"&gt;# --- Example Usage ---
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;user_question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What are the key performance metrics for the Q3 marketing campaign?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_rag_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--- Question ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- Answer ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Production Considerations: Security, Performance, and Governance
&lt;/h2&gt;

&lt;p&gt;Moving a Retrieval-Augmented Generation (RAG) system from a proof-of-concept to a production environment introduces a host of non-functional requirements that are critical for success. It's no longer just about getting the right answer; it's about delivering that answer securely, performantly, and in a way that aligns with your organization's governance and cost management principles. Building your RAG system on a lakehouse architecture with BigQuery and Iceberg provides a powerful foundation to address these challenges head-on, allowing you to leverage existing enterprise-grade features rather than building new solutions from scratch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leveraging Existing Lakehouse Security for AI Workloads
&lt;/h3&gt;

&lt;p&gt;One of the most significant advantages of this architecture is the ability to extend your existing data security and governance framework to your AI workloads. Your vector embeddings and source documents are not siloed in a separate, specialized database; they are first-class citizens within your BigQuery lakehouse, inheriting its robust security posture.&lt;br&gt;
&lt;strong&gt;Unified Access Control with IAM:&lt;/strong&gt;&lt;br&gt;
Access to both the source Apache Iceberg tables and the BigQuery vector indexes is managed through Google Cloud's Identity and Access Management (IAM). This means you can use the same roles and permissions you've already defined for your analytical workloads. There's no need to manage a separate set of credentials or access policies for your RAG application's data layer. A service account for your RAG application can be granted a fine-grained role, like &lt;code&gt;roles/bigquery.dataViewer&lt;/code&gt;, on only the specific datasets it needs to access.&lt;br&gt;
&lt;strong&gt;Fine-Grained Data Segmentation:&lt;/strong&gt;&lt;br&gt;
For sensitive data, you can enforce granular control using BigQuery's built-in security features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Column-Level Security (CLS):&lt;/strong&gt; Restrict access to specific columns containing sensitive information. For instance, you could prevent the embedding model pipeline from accessing columns with Personally Identifiable Information (PII) in your source Iceberg table, even if other parts of the table are needed.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Row-Level Security (RLS):&lt;/strong&gt; This is a game-changer for multi-tenant or department-specific RAG applications. You can create policies that filter which rows (i.e., which documents or text chunks) are visible to a user or service account based on their identity. An HR-specific RAG bot, for example, could be restricted to only query documents where &lt;code&gt;department = 'HR'&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Example of a Row-Level Access Policy&lt;/span&gt;
&lt;span class="c1"&gt;-- This policy ensures that users can only query vectors&lt;/span&gt;
&lt;span class="c1"&gt;-- related to their own department.&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;ACCESS&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="n"&gt;department_filter&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;my_project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;document_embeddings&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"group:sales-team@example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;department&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Sales'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Auditing and Lineage:&lt;/strong&gt;&lt;br&gt;
Every query, including vector searches, is logged in Cloud Audit Logs. This provides an immutable record of what data was accessed, by whom, and when. This is invaluable for compliance, security audits, and debugging. You can trace a specific generated response back to the exact &lt;code&gt;VECTOR_SEARCH&lt;/code&gt; query that was run, providing full data lineage for your AI application's knowledge retrieval step.&lt;br&gt;
&lt;strong&gt;Network Security with VPC Service Controls:&lt;/strong&gt;&lt;br&gt;
For organizations with stringent data exfiltration requirements, you can place your BigQuery datasets and the underlying Cloud Storage buckets for your Iceberg tables within a VPC Service Controls perimeter. This creates a virtual network boundary, ensuring that your sensitive data and embeddings can only be accessed by authorized services and networks, effectively preventing data from leaving your trusted environment.&lt;/p&gt;
&lt;h3&gt;
  
  
  Benchmarking Indexing and Query Performance
&lt;/h3&gt;

&lt;p&gt;Performance in a RAG system is a multi-faceted concern, primarily revolving around the trade-off between search quality (recall) and speed (latency). A systematic benchmarking approach is essential to find the right balance for your application's Service Level Objectives (SLOs).&lt;br&gt;
&lt;strong&gt;Indexing Performance:&lt;/strong&gt;&lt;br&gt;
The creation of a vector index in BigQuery is an asynchronous, back-end process. The time it takes is influenced by the number of vectors, their dimensionality, and the index configuration. While you don't need to manage the underlying compute, you should monitor the build process.&lt;br&gt;
You can track the progress of index creation using the &lt;code&gt;INFORMATION_SCHEMA&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;coverage_percentage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;last_refresh_time&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="nv"&gt;`my_project.my_dataset.INFORMATION_SCHEMA.VECTOR_INDEXES`&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
&lt;span class="k"&gt;table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'document_embeddings'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An index is queryable before it reaches 100% coverage, but performance and recall will improve as it approaches full coverage. For production systems, your data ingestion pipeline should have a step to verify that the &lt;code&gt;coverage_percentage&lt;/code&gt; is 100 before routing live traffic to a newly refreshed index.&lt;br&gt;
&lt;strong&gt;Query Performance: The Latency vs. Recall Trade-off:&lt;/strong&gt;&lt;br&gt;
Approximate Nearest Neighbor (ANN) search, which powers &lt;code&gt;VECTOR_SEARCH&lt;/code&gt;, is designed to be fast by trading perfect accuracy for speed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Latency:&lt;/strong&gt; The time it takes for the &lt;code&gt;VECTOR_SEARCH&lt;/code&gt; function to return results. This is a critical metric for user-facing applications.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Recall:&lt;/strong&gt; The percentage of the true nearest neighbors that are returned by the query. 100% recall would be equivalent to a brute-force (exact) search, which is computationally expensive.
In BigQuery, the primary tuning knob for this trade-off is the &lt;code&gt;num_lists_to_search&lt;/code&gt; option within &lt;code&gt;ivf_options&lt;/code&gt;. A higher value instructs the query engine to scan more of the index's "inverted file" lists, increasing the probability of finding the true nearest neighbors (higher recall) at the cost of increased processing and higher latency.
&lt;strong&gt;A Practical Benchmarking Strategy:&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Establish Ground Truth:&lt;/strong&gt; On a representative sample of your data (e.g., 10k-100k vectors), run a brute-force distance calculation to find the true top-K nearest neighbors for a set of test queries. This is your "ground truth."&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Run Experiments:&lt;/strong&gt; Execute the &lt;code&gt;VECTOR_SEARCH&lt;/code&gt; function against the full, indexed dataset using the same test queries. Vary the &lt;code&gt;num_lists_to_search&lt;/code&gt; parameter for each run (e.g., 10, 20, 50, 100).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Measure and Plot:&lt;/strong&gt; For each parameter setting, calculate the average query latency and the recall (i.e., &lt;code&gt;(number of true neighbors found) / K&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Find the Sweet Spot:&lt;/strong&gt; Plot your results on a latency vs. recall curve. This visualization will help you and your product stakeholders make an informed decision, choosing the lowest &lt;code&gt;num_lists_to_search&lt;/code&gt; value that meets your application's minimum recall requirement, thereby optimizing for the lowest possible latency and cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cost Management Strategies for Embedding and Search Operations
&lt;/h3&gt;

&lt;p&gt;Generative AI workloads can become expensive if not managed carefully. A proactive approach to cost optimization is crucial for building a sustainable, production-grade RAG system.&lt;br&gt;
&lt;strong&gt;1. Embedding Costs:&lt;/strong&gt;&lt;br&gt;
The initial and ongoing cost of generating embeddings via an external model API is often the largest component.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Incremental Embedding:&lt;/strong&gt; This is the most effective cost-control strategy. Instead of re-embedding your entire corpus on every update, leverage Apache Iceberg's time-travel capabilities. By querying a snapshot of the table from the last time the pipeline ran, you can easily identify only the new or modified rows that require embedding. This transforms a potentially massive, expensive batch job into a small, efficient, and low-cost incremental update.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Model Selection:&lt;/strong&gt; Carefully choose your embedding model. Higher-dimensional, state-of-the-art models are more expensive per token. Evaluate whether a smaller, more cost-effective model provides sufficient performance for your specific use case.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Indexing and Storage Costs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Storage:&lt;/strong&gt; BigQuery charges for the storage of the vector index itself. While typically much smaller than the raw data, this should be monitored via your Google Cloud billing reports.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Compute:&lt;/strong&gt; The &lt;code&gt;CREATE VECTOR INDEX&lt;/code&gt; job consumes BigQuery compute resources. Since this is often an infrequent operation (e.g., daily or weekly), its cost is usually predictable and manageable. Plan these jobs during off-peak hours if you are using a shared slot pool.
&lt;strong&gt;3. Query Costs:&lt;/strong&gt;
Vector search queries contribute to your BigQuery analysis costs.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Query Optimization:&lt;/strong&gt; As determined during benchmarking, use the lowest possible &lt;code&gt;num_lists_to_search&lt;/code&gt; that meets your recall SLOs. This directly reduces the amount of data processed per query.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Caching:&lt;/strong&gt; For frequently asked questions or popular search terms, implement a caching layer (e.g., Redis, Memorystore) in your application to store the retrieved context. This avoids re-running the same &lt;code&gt;VECTOR_SEARCH&lt;/code&gt; query repeatedly, saving significant cost and reducing latency.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Edition and Capacity Planning:&lt;/strong&gt; Align your BigQuery edition with your workload. For applications with predictable, high query volumes, purchasing reserved slots with the Enterprise or Enterprise Plus editions can be more cost-effective than the on-demand model. BigQuery's autoscaling is ideal for handling spiky, unpredictable traffic without overprovisioning.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Monitoring and Alerting:&lt;/strong&gt; Use the &lt;code&gt;INFORMATION_SCHEMA.JOBS&lt;/code&gt; view to monitor the bytes processed by your vector search queries. Set up Cloud Billing budgets and alerts to get notified if costs exceed your forecasts, allowing you to take corrective action before you get a surprise at the end of the month.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion: Unifying AI and Data on the Lakehouse
&lt;/h2&gt;

&lt;p&gt;We've journeyed through a paradigm shift in building AI applications—moving from complex, fragmented architectures to a streamlined, powerful model centered on the data lakehouse. By integrating vector search capabilities directly into BigQuery and leveraging the open standard of Apache Iceberg, we've demonstrated that production-grade RAG is not just feasible but fundamentally more efficient and secure when AI is brought &lt;em&gt;to&lt;/em&gt; the data. This approach dissolves the traditional boundaries between analytical and AI workloads, paving the way for a new generation of data-driven, intelligent applications built directly on your source of truth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recap of Key Benefits: Reduced Complexity and Enhanced Security
&lt;/h3&gt;

&lt;p&gt;The advantages of this unified architecture are immediate and substantial, primarily revolving around simplification and fortification.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Reduced Architectural Complexity:&lt;/strong&gt; The most significant benefit is the radical simplification of the tech stack. By eliminating the need for a separate, specialized vector database, you collapse multiple systems into one. This means:&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;No More Data Duplication:&lt;/strong&gt; Your embeddings and source data coexist, managed under a single storage layer with Apache Iceberg. This eradicates complex and brittle ETL pipelines designed solely to sync data between your analytical store and a vector database.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Streamlined MLOps:&lt;/strong&gt; The entire lifecycle—from data ingestion and transformation in BigLake, to embedding generation with remote models, to indexing and querying with &lt;code&gt;VECTOR_SEARCH&lt;/code&gt;—occurs within a cohesive GCP environment. This simplifies orchestration, monitoring, and maintenance.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Unified Interface:&lt;/strong&gt; Developers and analysts can leverage familiar SQL to perform sophisticated similarity searches, lowering the barrier to entry and accelerating development cycles.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Enhanced Data Security and Governance:&lt;/strong&gt; Centralizing your vector data within the lakehouse means it inherits the robust security posture of your core data platform.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Single Governance Pane:&lt;/strong&gt; Data security is no longer a federated problem. All your existing BigQuery security controls—IAM permissions, column-level security, row-level access policies, and data masking—apply directly to your vector embeddings.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Minimized Data Egress:&lt;/strong&gt; Sensitive data doesn't need to be exfiltrated to another system for processing. It remains within the secure perimeter of your Google Cloud project, drastically reducing the attack surface and simplifying compliance audits.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Future of In-Database Vector Processing
&lt;/h3&gt;

&lt;p&gt;What we've built here is not an endpoint but a glimpse into the future of data platforms. The trend is clear: databases are evolving from passive repositories into active, intelligent engines capable of handling diverse workloads, including AI. We can anticipate several exciting developments on this front:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Deeper Model Integration:&lt;/strong&gt; Expect tighter integration of embedding models directly within the database engine. Imagine SQL functions that can generate embeddings on the fly from raw text or image data without calling an external endpoint, further simplifying the pipeline.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Advanced, Automated Indexing:&lt;/strong&gt; While the IVFFlat index is powerful, the future will likely bring more advanced, native indexing strategies like HNSW (Hierarchical Navigable Small World) to platforms like BigQuery. This will offer even greater performance and flexibility, with the database optimizer intelligently choosing the best strategy for your query.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;End-to-End In-Database RAG:&lt;/strong&gt; The ultimate evolution is a fully managed, in-database RAG function. A single SQL query or stored procedure could potentially take a user's question, perform the vector search, retrieve context, pass it to a generative model like Gemini, and return a fully synthesized answer—all as a single, atomic database operation.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Power of Open Formats:&lt;/strong&gt; Apache Iceberg's role here is critical. It ensures that as these powerful in-database AI capabilities emerge across different engines (like Spark, Flink, or Trino), your underlying data remains open, accessible, and free from vendor lock-in. Your vector-enabled data lakehouse becomes a truly interoperable asset.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Next Steps and Further Resources
&lt;/h3&gt;

&lt;p&gt;You now have the architectural blueprint for building a scalable, secure, and efficient RAG system on the lakehouse. The next step is to put it into practice.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Experiment and Adapt:&lt;/strong&gt; Take the principles and code from this article and apply them to your own datasets. Start with a small-scale proof-of-concept to understand the nuances of index tuning and query performance for your specific use case.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Explore Advanced Features:&lt;/strong&gt; Dive deeper into BigQuery's capabilities, such as using remote models for embedding generation or integrating the vector search results into complex analytical queries to uncover new insights.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Contribute to the Community:&lt;/strong&gt; Engage with the open-source communities around Apache Iceberg and related projects. Sharing your findings and contributing to the ecosystem benefits everyone building on the modern data stack.
To continue your journey, here are some valuable resources:&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Google Cloud Documentation:&lt;/strong&gt; &lt;a href="https://cloud.google.com/bigquery/docs/vector-search-overview" rel="noopener noreferrer"&gt;BigQuery Vector Search Overview&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Apache Iceberg Project:&lt;/strong&gt; &lt;a href="https://iceberg.apache.org/docs/latest/" rel="noopener noreferrer"&gt;Official Iceberg Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Google Cloud Blog:&lt;/strong&gt; &lt;a href="https://cloud.google.com/blog/products/data-analytics" rel="noopener noreferrer"&gt;Building AI Applications on BigQuery&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Vertex AI Model Garden:&lt;/strong&gt; &lt;a href="https://cloud.google.com/vertex-ai/docs/generative-ai/model-garden" rel="noopener noreferrer"&gt;Explore Text Embedding Models&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>lakehouse</category>
      <category>bigquery</category>
      <category>apacheiceberg</category>
      <category>rag</category>
    </item>
    <item>
      <title>Autonomous Workspace Orchestration with Antigravity 2.0</title>
      <dc:creator>Vo Tu Duc</dc:creator>
      <pubDate>Thu, 02 Jul 2026 12:41:19 +0000</pubDate>
      <link>https://dev.to/gde/autonomous-workspace-orchestration-with-antigravity-20-adj</link>
      <guid>https://dev.to/gde/autonomous-workspace-orchestration-with-antigravity-20-adj</guid>
      <description>&lt;p&gt;Even the most advanced enterprise systems are tethered to a costly paradox: manual bottlenecks that introduce critical errors, security risks, and slow innovation. These hidden operational anchors are the friction preventing your organization from realizing its full potential.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1szvckrzcgfgbtx6t2el.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1szvckrzcgfgbtx6t2el.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge: Manual Bottlenecks in Modern Enterprise Operations
&lt;/h2&gt;

&lt;p&gt;In an era defined by cloud-native architectures, microservices, and declarative infrastructure, a persistent and costly paradox remains at the heart of enterprise operations. We have built systems capable of immense scale and resilience, yet they are often tethered to manual, human-driven processes that act as operational anchors. These bottlenecks aren't just minor inefficiencies; they are critical points of failure, introducing latency, human error, and security vulnerabilities into our most important workflows. They represent the friction that slows down innovation, drains resources, and prevents organizations from realizing the full potential of their digital investments. Before we can orchestrate an autonomous workspace, we must first dissect the anatomy of these manual constraints.&lt;/p&gt;

&lt;h3&gt;
  
  
  Identifying the High Cost of Manual Invoice Reconciliation
&lt;/h3&gt;

&lt;p&gt;To ground this challenge in reality, consider a ubiquitous and deceptively complex business process: accounts payable invoice reconciliation. On the surface, it seems simple. In practice, it's a classic example of a high-friction, manual workflow that silently bleeds enterprise resources.&lt;/p&gt;

&lt;p&gt;The typical process is a gauntlet of context-switching and swivel-chair integration:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcs1pagkr3a4jha2kfqdu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcs1pagkr3a4jha2kfqdu.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An invoice arrives, often as a PDF attached to an email, with no standardized format.&lt;/li&gt;
&lt;li&gt;A finance professional must manually open the document and visually identify key data points: invoice number, date, vendor, line items, and total amount.&lt;/li&gt;
&lt;li&gt;They then pivot to an ERP system like SAP or NetSuite to find the corresponding Purchase Order (PO).&lt;/li&gt;
&lt;li&gt;Next, they might need to access a separate logistics or warehouse management system to verify that the goods or services were actually received.&lt;/li&gt;
&lt;li&gt;Finally, after cross-referencing data across three or more disparate systems, they manually key the validated information into the accounting platform for payment processing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each step is a potential failure point. A typo during data entry can lead to a costly overpayment. A missed detail can result in a delayed payment, damaging a crucial vendor relationship. When an exception occurs—a price mismatch, a missing PO number, an unexpected tax line—the process grinds to a halt, requiring escalations and further manual investigation.&lt;/p&gt;

&lt;p&gt;The cost is multifaceted and staggering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Direct Labor Cost:&lt;/strong&gt; Countless hours are spent by skilled professionals on repetitive, low-value data transcription.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Error Cost:&lt;/strong&gt; Financial leakage from incorrect payments and the labor required to remediate those errors.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Opportunity Cost:&lt;/strong&gt; The finance team is bogged down in operational minutiae instead of focusing on strategic analysis, forecasting, and financial modeling.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Scalability Ceiling:&lt;/strong&gt; The process scales linearly. To process twice the number of invoices, you must hire nearly twice the number of people. This model is fundamentally unsustainable in a high-growth environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Traditional Scripting and Basic Email Automation Fall Short
&lt;/h3&gt;

&lt;p&gt;The industry has attempted to solve this problem for years with traditional workflow automation tools, but these efforts have consistently fallen short. The reason is simple: they try to pave the cowpath rather than engineer a new highway. They automate the &lt;em&gt;clicks&lt;/em&gt;, not the &lt;em&gt;intent&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Brittle Scripts and UI Automation:&lt;/strong&gt; Custom scripts (e.g., browser automation with Selenium) or first-generation Robotic Process Automation (RPA) tools are notoriously fragile. They rely on hard-coded selectors like CSS IDs or XPaths to navigate web interfaces. The moment a developer ships a minor UI update—changing a button's ID from &lt;code&gt;submit-btn&lt;/code&gt; to &lt;code&gt;primary-submit-btn&lt;/code&gt;—the automation shatters. The same applies to document parsing; a vendor slightly altering their invoice PDF layout breaks the template-based extraction logic. The result is a system that requires constant, expensive maintenance, turning automation engineers into full-time script mechanics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API-Centric Integration Platforms:&lt;/strong&gt; Tools like iPaaS (Integration Platform as a Service) are powerful for connecting modern applications that expose clean, well-documented APIs. They excel at system-to-system communication. However, they hit a wall when confronted with the messy reality of enterprise workflows. How do you "API into" a PDF invoice from a small vendor? How do you interact with a legacy mainframe system that has no web services layer? These platforms create islands of efficient automation, but the manual, unstructured gaps between those islands remain, and that's precisely where the most significant bottlenecks lie.&lt;/p&gt;

&lt;p&gt;These traditional approaches fail because they are fundamentally deterministic. They are programmed with a fixed set of rules and expect a predictable environment. They lack the cognitive and adaptive capabilities to handle the ambiguity, variation, and constant change inherent in real-world business operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introducing the Autonomous Operations Sidecar Pattern
&lt;/h3&gt;

&lt;p&gt;To transcend these limitations, we need a new architectural pattern—one that shifts from rigid, pre-programmed automation to adaptive, context-aware autonomy. We call this the &lt;strong&gt;Autonomous Operations Sidecar&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This pattern borrows its name from the well-known sidecar concept in microservices and service mesh architectures, but applies it to human-centric workflows. Instead of a container running alongside a service, imagine an autonomous agent running alongside a human operator or a business process. It doesn't seek to rip-and-replace existing systems but to augment and accelerate them by handling the manual, repetitive tasks that traditional automation cannot.&lt;/p&gt;

&lt;p&gt;The Autonomous Operations Sidecar is defined by three core characteristics that set it apart:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Multi-Modal Perception:&lt;/strong&gt; Unlike a script that only understands APIs or a UI automation bot that only sees the DOM, a sidecar agent perceives the workspace holistically. It uses a combination of computer vision to understand UIs visually (like a human does), natural language processing to read documents and emails, and traditional API integrations to interact with structured systems. It can look at a PDF, understand its contents, find the relevant portal, and complete the task, all without relying on fragile selectors.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Adaptive Reasoning:&lt;/strong&gt; The sidecar is goal-oriented, not script-oriented. Its objective isn't "click button with ID &lt;code&gt;submit-btn&lt;/code&gt;." Its objective is "submit this verified invoice." If a button moves, changes color, or its text is slightly altered, the agent can still identify its function based on visual context and language understanding, and adapt its execution plan in real-time. This resilience to change dramatically reduces the maintenance burden.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Human-in-the-Loop Collaboration:&lt;/strong&gt; The sidecar is not a black box. It's designed for collaboration. When it encounters a novel situation or has low confidence in its interpretation of a document, it can pause, flag the exception, and present it to a human operator for a decision. It then learns from that interaction, continuously improving its model. This creates a powerful partnership, freeing humans to focus on high-judgment edge cases while the agent handles the high-volume, predictable work.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This pattern represents a fundamental shift from automating static procedures to orchestrating dynamic, autonomous capabilities. It's the architectural foundation required to finally eliminate the manual bottlenecks that have plagued operations for decades.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecting the Solution: An Autonomous System Blueprint
&lt;/h2&gt;

&lt;p&gt;To achieve true autonomous orchestration, we move beyond simple, linear automation scripts. The architecture isn't just a chain of triggers and actions; it's a cohesive, intelligent system designed for resilience, adaptability, and stateful execution. This blueprint reveals how we combine a powerful AI engine with a robust cloud backend to create a system that can independently manage complex workspace objectives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Components: Antigravity 2.0, Google Workspace, Firestore, and Google Chat
&lt;/h3&gt;

&lt;p&gt;The elegance of this solution lies in the synergy between four key pillars. Each component plays a distinct and critical role, forming a closed-loop system that can sense, reason, act, and communicate.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Antigravity 2.0 (The Orchestration Engine):&lt;/strong&gt; This is the brain of the operation. Antigravity 2.0 is not a mere task runner; it's a goal-oriented autonomous agent. Given a high-level objective—like "Process all incoming client proposals"—it autonomously formulates a plan, decomposes it into executable steps, interacts with other services via their APIs, and adapts its strategy based on the outcomes. It handles the complex logic, decision-making, and error recovery that traditionally require human intervention.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Google Workspace (The Digital Environment):&lt;/strong&gt; This is the system's field of operation. Google Drive acts as the data inbox, Docs and Sheets serve as unstructured and structured data sources, and Calendar handles scheduling and time-based triggers. Antigravity 2.0 interacts with these tools as an active participant, reading, understanding, creating, and modifying resources to achieve its goals.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Firestore (The Persistent Memory):&lt;/strong&gt; A critical component for true autonomy is state management. Firestore, a flexible NoSQL cloud database, serves as the system's memory. It tracks the state of every goal and sub-task from initiation to completion. This ensures durability and resilience. If a process needs to wait hours for human approval or if a component temporarily fails, the system can pause and resume seamlessly, picking up exactly where it left off without losing context.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Google Chat (The Communication Interface):&lt;/strong&gt; Autonomy doesn't mean isolation. Google Chat provides the essential human-in-the-loop communication channel. The system uses it to send real-time notifications, request approvals for critical steps, and escalate exceptions it cannot resolve on its own. This creates a collaborative relationship where the autonomous agent handles the bulk of the work, only engaging human counterparts when their judgment or authority is explicitly required.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Visualizing the End-to-End Workflow: From Drive to Transaction
&lt;/h3&gt;

&lt;p&gt;Let's trace a tangible example to see how these components work in concert. The high-level goal is: "When a new partner agreement is uploaded to a specific Drive folder, extract the key terms, secure manager approval, and log it as a new transaction."&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Sensing (Trigger):&lt;/strong&gt; A user uploads &lt;code&gt;Partner_Agreement_Q3.docx&lt;/code&gt; into the designated &lt;code&gt;/Agreements/Pending&lt;/code&gt; folder in Google Drive.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Notification:&lt;/strong&gt; A Google Cloud Function, subscribed to Drive events, is triggered. It captures the file metadata and sends a payload to the Antigravity 2.0 engine, initiating the process.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;State Initialization:&lt;/strong&gt; Antigravity 2.0 creates a new document in Firestore to track the goal, setting the initial state.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"goalId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"p-agr-q3-xyz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NEW_AGREEMENT_DETECTED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sourceFileId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"receivedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-10-27T10:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"history"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Reasoning &amp;amp; Planning:&lt;/strong&gt; The engine analyzes its primary goal. It determines it needs to:

&lt;ul&gt;
&lt;li&gt;  Access and parse the Google Doc.&lt;/li&gt;
&lt;li&gt;  Identify entities: Partner Name, Agreement Value, Effective Date.&lt;/li&gt;
&lt;li&gt;  Validate the value; if it exceeds a predefined threshold (e.g., $10,000), require approval.&lt;/li&gt;
&lt;li&gt;  Construct and send an approval request to a specific Google Chat space.&lt;/li&gt;
&lt;li&gt;  Wait for a response.&lt;/li&gt;
&lt;li&gt;  Upon approval, create a new record in a "Master Transactions" Google Sheet.&lt;/li&gt;
&lt;li&gt;  Move the processed file from &lt;code&gt;/Pending&lt;/code&gt; to &lt;code&gt;/Processed&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  Update the final status in Firestore.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Execution &amp;amp; Interaction:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  Antigravity 2.0 calls the Google Drive and Docs APIs to read the document content.&lt;/li&gt;
&lt;li&gt;  It extracts the data and updates its state in Firestore: &lt;code&gt;status: "DATA_EXTRACTED"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  Seeing the value is $25,000, it calls the Google Chat API to post an interactive card with "Approve" and "Deny" buttons. The state changes to &lt;code&gt;status: "AWAITING_MANAGER_APPROVAL"&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Human-in-the-Loop:&lt;/strong&gt; A manager sees the notification in Google Chat, reviews the terms on the card, and clicks "Approve".&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Resumption &amp;amp; Completion:&lt;/strong&gt; The Chat API sends a webhook back to an endpoint monitored by Antigravity 2.0. The engine wakes up, validates the response, and updates the Firestore state to &lt;code&gt;status: "APPROVAL_RECEIVED"&lt;/code&gt;. It then proceeds to call the Google Sheets API to append the new transaction record and the Drive API to move the file.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Final State:&lt;/strong&gt; The process concludes. The final Firestore document reflects the successful outcome, providing a complete audit trail of the entire workflow.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"goalId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"p-agr-q3-xyz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"COMPLETED_SUCCESS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"approvedBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"manager@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"finalizedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-10-27T10:15:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step-by-Step Implementation Guide
&lt;/h2&gt;

&lt;p&gt;This section provides a detailed walkthrough for implementing an autonomous invoice processing workflow using Antigravity 2.0 and Google Workspace integrations. We will construct a system that automatically discovers new invoices in Google Drive, extracts key data, requests human approval via Google Chat, and persists the final state in Firestore.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites: Setting Up Your Antigravity and Google Cloud Environment
&lt;/h3&gt;

&lt;p&gt;Before writing any code, a foundational setup is required to ensure all services can communicate securely and effectively.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Google Cloud Project:&lt;/strong&gt; Ensure you have an active Google Cloud Project with billing enabled to house all cloud resources.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Enable APIs:&lt;/strong&gt; Enable the following APIs in the Google Cloud Console:

&lt;ul&gt;
&lt;li&gt;  Google Drive API&lt;/li&gt;
&lt;li&gt;  Google Sheets API&lt;/li&gt;
&lt;li&gt;  Google Chat API&lt;/li&gt;
&lt;li&gt;  Cloud Firestore API&lt;/li&gt;
&lt;li&gt;  Cloud Build API&lt;/li&gt;
&lt;li&gt;  Cloud Run API&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Service Account Configuration:&lt;/strong&gt; Create a Service Account (e.g., &lt;code&gt;antigravity-workspace-agent&lt;/code&gt;) and grant the following roles:

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;roles/drive&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;roles/sheets.editor&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;roles/chat.bot&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;roles/datastore.user&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;roles/cloudfunctions.invoker&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Antigravity 2.0 CLI:&lt;/strong&gt; Install and configure the Antigravity 2.0 CLI, authenticating it with your service account key.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Authenticate the Antigravity CLI&lt;/span&gt;
antigravity auth activate-service-account &lt;span class="nt"&gt;--key-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./path/to/your-key.json
antigravity config &lt;span class="nb"&gt;set &lt;/span&gt;project your-gcp-project-id
antigravity config &lt;span class="nb"&gt;set &lt;/span&gt;region us-central1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: Defining the Goal to Recursively Scan Google Drive
&lt;/h3&gt;

&lt;p&gt;The workflow begins with an Antigravity "Goal." We define this goal in a YAML file, which configures a scheduled Cloud Function to scan the target directory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;goal-find-invoices.yaml&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;goal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;findNewInvoices&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Periodically&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;scan&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'Incoming&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Invoices'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Google&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Drive&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;folder&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;new&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PDF&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;files."&lt;/span&gt;
&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;schedule&lt;/span&gt;
  &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
  &lt;span class="na"&gt;timezone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;America/Los_Angeles"&lt;/span&gt;
&lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scanDriveFolder&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodejs18&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./skills/scan-drive&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;rootFolderId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_GOOGLE_DRIVE_FOLDER_ID"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;./skills/scan-drive/index.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;googleapis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@antigravity/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GoogleAuth&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.googleapis.com/auth/drive.readonly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;drive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;recursiveScan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filesToProcess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;drive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`'&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;folderId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' in parents and trashed = false`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;files(id, name, mimeType)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mimeType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/vnd.google-apps.folder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subFolderFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;recursiveScan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;filesToProcess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filesToProcess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subFolderFiles&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mimeType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/pdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;filesToProcess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;filesToProcess&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scanDriveFolder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rootFolderId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ROOT_FOLDER_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Starting scan of folder: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rootFolderId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdfFileIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;recursiveScan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rootFolderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Found &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pdfFileIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; PDF files to process.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileId&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;pdfFileIds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;extractInvoiceMetadata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fileId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Extracting Invoice Metadata
&lt;/h3&gt;

&lt;p&gt;Once a PDF file is identified, the next Skill extracts structured data. In a production environment, you would integrate a tool like Google's Document AI, but for demonstration, we apply a regex-based parser before writing to a spreadsheet log.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;./skills/extract-metadata/index.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;googleapis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@antigravity/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pdf-parse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GoogleAuth&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.googleapis.com/auth/drive.readonly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.googleapis.com/auth/spreadsheets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;drive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sheets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sheets&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REGEX_PATTERNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Invoice&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*#:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;([&lt;/span&gt;&lt;span class="sr"&gt;A-Z0-9-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/From:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;([^\n]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\$\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.\d{2})&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;dueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Due&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*Date:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(\d{2}\/\d{2}\/\d{4})&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extractInvoiceMetadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fileId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fileId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;File ID not provided.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;drive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;fileId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;media&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;responseType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;arraybuffer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdfBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pdfBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fileId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;REGEX_PATTERNS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;N/A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;REGEX_PATTERNS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;N/A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;REGEX_PATTERNS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.00&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;dueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;REGEX_PATTERNS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dueDate&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;N/A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;extractedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spreadsheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;spreadsheetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_SPREADSHEET_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ExtractionLog!A1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;valueInputOption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USER_ENTERED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;
        &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dueDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fileId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extractedAt&lt;/span&gt;
      &lt;span class="p"&gt;]],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;requestApproval&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Building Interactive Human-in-the-Loop Approval Gates
&lt;/h3&gt;

&lt;p&gt;We construct a Human-in-the-Loop (HITL) checkpoint by sending an interactive Card to a Google Chat space.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;./skills/request-approval/index.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;googleapis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@antigravity/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestApproval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dueDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callbackHandlerUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSkillUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handleApprovalResponse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cardPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;cardsV2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;cardId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`invoice-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invoice Approval Request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Vendor: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
          &lt;span class="na"&gt;widgets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;keyValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;topLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invoice ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;keyValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;topLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Amount&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`$&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;keyValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;topLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Due Date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dueDate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;buttonList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                  &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Approve&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="na"&gt;openLink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;callbackHandlerUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?decision=approved&amp;amp;invoiceId=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;fileId=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fileId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
                      &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                  &lt;span class="p"&gt;},&lt;/span&gt;
                  &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Reject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="na"&gt;openLink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;callbackHandlerUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?decision=rejected&amp;amp;invoiceId=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;fileId=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fileId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
                      &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                  &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;]&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spaces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spaces/YOUR_CHAT_SPACE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cardPayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PENDING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;persistState&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Persisting State in Firestore
&lt;/h3&gt;

&lt;p&gt;Firestore tracks document state across asynchronous actions, decoupling execution steps cleanly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;./skills/persist-state/index.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Firestore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google-cloud/firestore&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@antigravity/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firestore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Firestore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;persistState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;N/A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid Invoice ID. Cannot persist state.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;docRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firestore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invoices&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;docRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;./skills/handle-approval-response/index.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@antigravity/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleApprovalResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-goog-authenticated-user-email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updatePayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;fileId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;approvedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;approvalTimestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;persistState&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updatePayload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;approved&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;executeTransaction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;action_response&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE_MESSAGE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Invoice &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; has been &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Executing the Final Transaction
&lt;/h3&gt;

&lt;p&gt;Once marked as &lt;code&gt;APPROVED&lt;/code&gt; in Firestore, the final API call runs to register the payment record.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;./skills/execute-transaction/index.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Firestore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google-cloud/firestore&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@antigravity/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firestore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Firestore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executeTransaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;docRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firestore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invoices&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;docRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;APPROVED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Invoice &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; not found or not approved.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;finalStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;transactionId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.accounting-system.com/v1/payments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ACCOUNTING_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;invoice_ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;transactionId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;finalStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PROCESSED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Error executing transaction for invoice &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Antigravity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;persistState&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;finalStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;processedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Code Deep Dive: Key Snippets and Logic
&lt;/h2&gt;

&lt;p&gt;Antigravity 2.0 acts as an translation and orchestration layer, converting abstract goals into structural plans.&lt;/p&gt;

&lt;h3&gt;
  
  
  Crafting the Antigravity 2.0 &lt;code&gt;/goal&lt;/code&gt; Prompt
&lt;/h3&gt;

&lt;p&gt;To convert unstructured user instructions into schema-conforming JSON execution plans, we configure Antigravity's LLM engine with a constrained system prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are "Antigravity", an expert AI orchestrator for enterprise workflow automation. Your primary function is to receive a high-level goal from a user and break it down into a precise, step-by-step plan of executable actions.

**CONTEXT:**
- The user requesting the action is: {{user_email}}
- The current date is: {{current_date}}
- You have access to the following tools and ONLY these tools:
  - `create_google_doc`: Creates a new Google Document. Parameters: `title` (string), `initial_content` (string, optional).
  - `create_google_sheet`: Creates a new Google Sheet. Parameters: `title` (string).
  - `create_calendar_event`: Creates a Google Calendar event. Parameters: `title` (string), `start_time` (ISO 8601 string), `end_time` (ISO 8601 string), `attendees` (array of strings).
  - `share_google_drive_file`: Shares a file. Parameters: `file_id` (string), `recipients` (array), `role` (string).
  - `send_email`: Sends an email. Parameters: `to` (array), `subject` (string), `body` (string).

**INSTRUCTIONS:**
1. Analyze the user's goal.
2. Deconstruct the goal into logical steps.
3. For each step, select the appropriate tool.
4. Output a strictly formatted JSON plan with explanations.

**USER GOAL:**
"{{user_goal}}"

**OUTPUT FORMAT (JSON ONLY):**
{
  "plan": [
    {
      "tool_name": "example_tool_name",
      "parameters": {
        "param1": "value1"
      },
      "reasoning": "Explanation here."
    }
  ],
  "summary": "Brief summary of the plan."
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Authenticating with Google APIs
&lt;/h3&gt;

&lt;p&gt;Our execution layer interacts securely using a Google Cloud Service Account configured with Domain-Wide Delegation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;google.auth&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;googleapiclient.discovery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;

&lt;span class="n"&gt;SERVICE_ACCOUNT_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;path/to/your/credentials.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;USER_TO_IMPERSONATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user@yourdomain.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;SCOPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://www.googleapis.com/auth/chat.messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_authenticated_service&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Builds and returns an authenticated Google API client.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_credentials_from_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;SERVICE_ACCOUNT_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SCOPES&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;delegated_creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;USER_TO_IMPERSONATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;chat&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;delegated_creds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error building service client: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Designing the Google Chat API Payload for Approval Cards
&lt;/h3&gt;

&lt;p&gt;The interactive Chat Card presents the formulated plan and exposed structured buttons to handle stateless callback actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cardsV2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cardId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"antigravity-approval-card"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"card"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Antigravity Plan Approval"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"subtitle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Please review the proposed action plan."&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"sections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Summary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"widgets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"textParagraph"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;b&amp;gt;Goal:&amp;lt;/b&amp;gt; Create and share a project kickoff document for 'Q3 Launch'."&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"widgets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"buttonList"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"buttons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Approve"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="nl"&gt;"onClick"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"handle_plan_approval"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"plan_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"plan-xyz-12345"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"approved"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="nl"&gt;"onClick"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"handle_plan_approval"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"plan_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"plan-xyz-12345"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"denied"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Conclusion: The Future is Autonomous
&lt;/h2&gt;

&lt;p&gt;We've journeyed through the architecture and mechanics of Antigravity 2.0, moving beyond the familiar territory of automation into the new frontier of autonomous orchestration. The distinction is critical: automation executes a script, while autonomy makes decisions. Antigravity 2.0 represents a fundamental paradigm shift, transforming our digital workspaces from static, manually-tended environments into dynamic, self-governing ecosystems. By embedding intelligent agents directly into our workflows, we're not just making processes faster; we're making them smarter, more resilient, and capable of adapting without human intervention. This is the dawn of the truly autonomous enterprise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quantifying the Impact: Reduced Errors and Accelerated Workflows
&lt;/h3&gt;

&lt;p&gt;The theoretical appeal of autonomy is compelling, but its value is realized in tangible, measurable outcomes. Organizations adopting this model are witnessing a dramatic transformation in their operational metrics.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Reduced Errors:&lt;/strong&gt; Human-driven configuration management is prone to drift and error. An autonomous system eliminates this entirely. We're seeing a near-total reduction in errors related to environment provisioning, dependency mismatches, and access control misconfigurations. Self-healing sidecars proactively detect and remediate issues—from restarting a failed service to re-provisioning a corrupted workspace—often before a human operator is even aware of the problem. This translates to higher system availability, tighter security, and a drastic reduction in costly downtime.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Accelerated Workflows:&lt;/strong&gt; Consider the lifecycle of a developer task. In a traditional model, it's punctuated by delays: waiting for environment setup, waiting for CI/CD pipeline access, waiting for security scans. With autonomous orchestration, these waiting periods vanish. Workflows that once took hours or days are now completed in minutes. The system anticipates needs, pre-warming environments and pre-fetching dependencies based on the context of an assigned ticket. This isn't just an incremental improvement; it's an orders-of-magnitude acceleration in development velocity and time-to-market.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Expanding the Sidecar Pattern to Other Business Processes
&lt;/h3&gt;

&lt;p&gt;While our focus has been on the developer workspace, the core principles of Antigravity 2.0—particularly the non-invasive sidecar pattern—are universally applicable. This architectural model allows us to attach autonomous capabilities to any existing process without re-architecting the core application. The potential is immense.&lt;/p&gt;

&lt;p&gt;Imagine an autonomous sidecar attached to your financial systems, capable of performing real-time compliance checks on transactions and flagging anomalies based on learned patterns, not just rigid rules. Picture an HR onboarding process where a sidecar autonomously provisions all necessary hardware, software licenses, and access permissions the moment a candidate signs their offer letter, tailoring the entire package to their specific role. From supply chain management that self-optimizes logistics to customer support systems that autonomously resolve common issues, the sidecar pattern provides a framework for embedding intelligence across the entire business fabric.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Steps: Your Path to Autonomous Enterprise Operations
&lt;/h3&gt;

&lt;p&gt;Embarking on the journey to autonomy may seem daunting, but it can be approached as a measured, iterative process. It's not about flipping a switch overnight; it's about building trust and demonstrating value at each stage.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Identify the Toil:&lt;/strong&gt; Begin by auditing your current operations. Where are the biggest bottlenecks? Which repetitive, manual tasks consume the most engineering time and are most prone to error? These high-friction, low-creativity processes are your prime candidates for the first autonomous sidecar.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Start in 'Advisory Mode':&lt;/strong&gt; Implement your first sidecar in a non-intrusive, read-only capacity. Have it monitor a process, analyze the data, and simply recommend actions in a Slack channel or ticketing system. For example, it could suggest, "The 'staging-web-app' workspace is under-provisioned for the scheduled load test. Recommend increasing CPU to 4 cores." This builds confidence in the agent's decision-making logic without ceding control.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Grant Incremental Autonomy:&lt;/strong&gt; Once the advisory sidecar has proven its reliability, begin granting it permission to act. Start with low-risk, reversible actions. Allow it to automatically scale a development environment, clean up unused artifacts, or restart a non-critical service. Gradually expand its scope of authority as your team's trust in the system solidifies.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Measure and Iterate:&lt;/strong&gt; True autonomy requires robust observability. Implement clear dashboards to track key metrics: workflow execution time, error rates before and after implementation, and Mean Time To Resolution (MTTR) for autonomously-handled incidents. Use this data to justify expansion and refine the intelligence of your autonomous agents.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The path to an autonomous enterprise is a marathon, not a sprint. By starting small, proving value, and building trust incrementally, you can begin to unlock the immense potential of a truly self-orchestrating operational model. The future isn't just automated; it's autonomous, and it's time to build it.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>antigravity</category>
      <category>workplace</category>
      <category>googlecloud</category>
      <category>autonomous</category>
    </item>
  </channel>
</rss>
