<?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: Saravana Dev</title>
    <description>The latest articles on DEV Community by Saravana Dev (@saravana_dev).</description>
    <link>https://dev.to/saravana_dev</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%2F4099646%2F5d2e98d8-a0b3-4f1d-baa1-0e8ae63af0d3.jpg</url>
      <title>DEV Community: Saravana Dev</title>
      <link>https://dev.to/saravana_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saravana_dev"/>
    <language>en</language>
    <item>
      <title>Building an AI Question Paper Generator: Conquering Google Cloud Document AI, Firestore Vector Search, and Gemini</title>
      <dc:creator>Saravana Dev</dc:creator>
      <pubDate>Sat, 29 Aug 2026 12:53:32 +0000</pubDate>
      <link>https://dev.to/saravana_dev/building-an-ai-question-paper-generator-conquering-google-cloud-document-ai-firestore-vector-4dpa</link>
      <guid>https://dev.to/saravana_dev/building-an-ai-question-paper-generator-conquering-google-cloud-document-ai-firestore-vector-4dpa</guid>
      <description>&lt;p&gt;As part of the &lt;strong&gt;Gen AI Academy APAC&lt;/strong&gt;, I set out to solve a major pain point for educators: manually sifting through textbooks to create grade-appropriate question papers. &lt;/p&gt;

&lt;p&gt;I built an automated &lt;strong&gt;Question Paper Generator&lt;/strong&gt; using a Serverless Next.js stack, a Retrieval-Augmented Generation (RAG) architecture, and the complete Google Cloud AI suite. Teachers simply upload a textbook chapter (PDF), specify the grade and subject, and let the AI generate a fully formatted assessment quiz.&lt;/p&gt;

&lt;p&gt;While the architecture sounds straightforward, orchestrating these enterprise-grade APIs in a serverless environment presented several intense technical hurdles. Here is a deep dive into the architecture, the specific roadblocks I hit, and how I ultimately solved them.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏗️ The RAG Architecture
&lt;/h2&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%2Fggjckx50e4jqsa0v8b73.jpg" 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%2Fggjckx50e4jqsa0v8b73.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The application is built on &lt;strong&gt;Next.js 15&lt;/strong&gt; and deployed to &lt;strong&gt;Google Cloud Run&lt;/strong&gt;. The pipeline flows as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Document Extraction&lt;/strong&gt;: The PDF is uploaded and sent to &lt;strong&gt;Google Cloud Document AI&lt;/strong&gt; (Document OCR Processor) to extract the raw text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunking &amp;amp; Embeddings&lt;/strong&gt;: The text is chunked into logical paragraphs and sent to &lt;strong&gt;Vertex AI&lt;/strong&gt; (&lt;code&gt;text-embedding-004&lt;/code&gt;) to generate dense vector embeddings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector Database&lt;/strong&gt;: The embeddings and metadata (Grade, Subject) are stored seamlessly in &lt;strong&gt;Firestore&lt;/strong&gt; using native &lt;code&gt;VectorValue&lt;/code&gt; support.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval &amp;amp; Generation&lt;/strong&gt;: When a teacher requests a quiz, the query is embedded, and a &lt;code&gt;findNearest&lt;/code&gt; Vector Search runs on Firestore. The retrieved context is passed to &lt;strong&gt;Google Gen AI&lt;/strong&gt; (&lt;code&gt;gemini-3.5-flash&lt;/code&gt;) to synthesize the structured question paper.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🐛 The Technical Challenges &amp;amp; How I Solved Them
&lt;/h2&gt;

&lt;p&gt;Building an end-to-end pipeline using cutting-edge SDKs often means dealing with strict schema validations and opaque error codes. Here are the major technical gotchas I faced.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Document AI Region Endpoint Mismatch
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Challenge:&lt;/strong&gt;&lt;br&gt;
I provisioned a Document OCR processor in the &lt;code&gt;asia-south1&lt;/code&gt; region. However, when my Node.js client attempted to send a processing request using the processor's full resource name, it threw a cryptic HTTP 400 error: &lt;br&gt;
&lt;code&gt;"7 PERMISSION_DENIED: Permission 'documentai.processors.processOnline' denied on resource... (or it may not exist)."&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
By default, the &lt;code&gt;DocumentProcessorServiceClient&lt;/code&gt; routes all traffic to the global &lt;code&gt;us-documentai.googleapis.com&lt;/code&gt; endpoint. The US gateway has no knowledge of processors in Asia, hence the "does not exist" error. &lt;br&gt;
To fix this, I had to explicitly override the API endpoint when initializing the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&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;DocumentProcessorServiceClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiEndpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;asia-south1-documentai.googleapis.com&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Strict gRPC Byte Transport
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Challenge:&lt;/strong&gt;&lt;br&gt;
When sending the PDF payload to Document AI, I initially converted the file to a &lt;code&gt;base64&lt;/code&gt; encoded string (a standard practice for JSON REST APIs). However, the API instantly rejected the request with &lt;code&gt;INVALID_ARGUMENT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
The underlying transport for the Google Cloud Node SDK is heavily reliant on Protobuf and gRPC. The schema strictly expects a raw byte array for the document content, not a base64 encoded string. I resolved this by bypassing the string conversion and passing the raw Node &lt;code&gt;Buffer&lt;/code&gt; directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;processorName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;rawDocument&lt;/span&gt;&lt;span class="p"&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;pdfBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Passed as raw Buffer, not base64 string&lt;/span&gt;
    &lt;span class="na"&gt;mimeType&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/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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Hitting the Document AI Synchronous Page Limits
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Challenge:&lt;/strong&gt;&lt;br&gt;
During testing, an 18-page textbook chapter completely crashed the pipeline with: &lt;code&gt;Document pages exceed the limit: 15 got 18.&lt;/code&gt; &lt;br&gt;
By default, the Document OCR processor restricts synchronous processing (&lt;code&gt;processDocument&lt;/code&gt;) to a maximum of 15 pages to prevent timeout issues, forcing developers to use the asynchronous batch endpoint for larger files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
I didn't want to build a complex webhook-polling system for a synchronous UI just to process 3 extra pages. Digging into the API reference, I found a hidden gem: &lt;strong&gt;Imageless Mode&lt;/strong&gt;. By injecting &lt;code&gt;imagelessMode: true&lt;/code&gt; into the &lt;code&gt;ProcessRequest&lt;/code&gt;, Document AI bypasses heavy image quality scoring, which effectively doubles the synchronous page limit to &lt;strong&gt;30 pages&lt;/strong&gt;!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;processOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ocrConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;advancedOcrOptions&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;imagelessMode=true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// Doubled the page limit!&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;
  
  
  4. Firestore Composite Vector Indexes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Challenge:&lt;/strong&gt;&lt;br&gt;
Firestore now natively supports Vector Search! However, my RAG pipeline needed to filter document chunks by metadata &lt;em&gt;before&lt;/em&gt; running the cosine similarity search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vectorQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;coll&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;grade&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;==&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gradeLevel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subject&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;==&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findNearest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;embedding&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FieldValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryEmbedding&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;distanceMeasure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COSINE&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This threw a &lt;code&gt;FAILED_PRECONDITION&lt;/code&gt; error. While standard scalar queries can dynamically build indexes, Vector Search &lt;em&gt;strictly&lt;/em&gt; requires a pre-built Composite Vector Index when combined with &lt;code&gt;where&lt;/code&gt; clauses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
I had to drop into the &lt;code&gt;gcloud&lt;/code&gt; CLI and construct a highly specific composite index command that included both the scalar fields (&lt;code&gt;grade&lt;/code&gt;, &lt;code&gt;subject&lt;/code&gt;) and the vector configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud firestore indexes composite create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-ai-project &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--collection-group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;document_chunks &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query-scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COLLECTION &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--field-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ASCENDING,field-path&lt;span class="o"&gt;=&lt;/span&gt;grade &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--field-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ASCENDING,field-path&lt;span class="o"&gt;=&lt;/span&gt;subject &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--field-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vector-config&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{"dimension":"768","flat": "{}"}'&lt;/span&gt;,field-path&lt;span class="o"&gt;=&lt;/span&gt;embedding
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: Building this index takes a few minutes, so grab a coffee after running the command!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Swapping ADK for the Native &lt;code&gt;@google/genai&lt;/code&gt; SDK
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Challenge:&lt;/strong&gt;&lt;br&gt;
The original codebase utilized the Agent Development Kit (ADK) to stream the generation response. However, dealing with the internal, undocumented &lt;code&gt;InvocationEvent&lt;/code&gt; schema of the &lt;code&gt;InMemoryRunner&lt;/code&gt; caused unexpected TypeScript errors and silent failures (&lt;code&gt;No content generated&lt;/code&gt;) when parsing the final text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
To ensure absolute robustness for production, I stripped out the complex ADK streaming loop and migrated the final generation step directly to the native &lt;code&gt;@google/genai&lt;/code&gt; SDK. It took only 10 lines of code, completely bypassed the stream parsing nightmare, and natively supported the lightning-fast &lt;code&gt;gemini-3.5-flash&lt;/code&gt; model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GoogleGenAI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google/genai&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;ai&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;GoogleGenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REGION&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="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini-3.5-flash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&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;response&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🚀 The Final Result
&lt;/h2&gt;

&lt;p&gt;Deploying this architecture to a fully serverless Next.js container on Cloud Run was incredibly rewarding. By combining Document AI's world-class OCR with Vertex AI's embedding models and Firestore's seamless Vector Search, the system can instantly generate hyper-relevant, grade-appropriate question papers backed directly by a teacher's syllabus!&lt;/p&gt;

&lt;p&gt;If you're building RAG applications on Google Cloud, I highly recommend leveraging Firestore's native Vector Search—just remember to configure your API endpoints, pass your raw Buffers, and pre-build those composite indexes! &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have you built anything with the new Firestore Vector Search or Gemini? Drop a comment below!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>googlecloud</category>
      <category>rag</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
