<?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: Google AI</title>
    <description>The latest articles on DEV Community by Google AI (googleai).</description>
    <link>https://dev.to/googleai</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%2Forganization%2Fprofile_image%2F11026%2F386b14d3-cc9a-4270-aba0-3e41cdfb9d85.jpg</url>
      <title>DEV Community: Google AI</title>
      <link>https://dev.to/googleai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/googleai"/>
    <language>en</language>
    <item>
      <title>Claude Haiku 5.5 is available on Google Cloud</title>
      <dc:creator>Jen Harvey</dc:creator>
      <pubDate>Wed, 07 Oct 2026 18:06:52 +0000</pubDate>
      <link>https://dev.to/googleai/claude-haiku-55-is-available-on-google-cloud-4gn0</link>
      <guid>https://dev.to/googleai/claude-haiku-55-is-available-on-google-cloud-4gn0</guid>
      <description>&lt;p&gt;Hey developers! &lt;/p&gt;

&lt;p&gt;If you’ve been building autonomous agents or multi-agent orchestrations, you already know the pain: latency degrades the user experience, and high token volume stretches production budgets.&lt;/p&gt;

&lt;p&gt;Developer choice is important to us so today, we're excited to share that Anthropic’s Claude Haiku 5.5 is officially available on Google Cloud!&lt;/p&gt;

&lt;p&gt;As the fastest, most efficient member of the Claude 5.5 family, Claude Haiku 5.5 is engineered from the ground up for high-volume, latency-sensitive agentic work. It is designed to be the ultimate supporting player in your agent systems. &lt;/p&gt;

&lt;p&gt;Is it time for you to try it? -&amp;gt; &lt;a href="https://console.cloud.google.com/agent-platform/publishers/anthropic/model-garden/claude-haiku-5-5" rel="noopener noreferrer"&gt;https://console.cloud.google.com/agent-platform/publishers/anthropic/model-garden/claude-haiku-5-5&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And enjoy this Haiku about Claude Haiku 5.5:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fast subagent flies,&lt;br&gt;
Model Garden hosts the spark,&lt;br&gt;
Agents run in light.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>stop-using-complex-urls-for-your-demos.us-central1.run.app</title>
      <dc:creator>Ruchika Goel</dc:creator>
      <pubDate>Wed, 07 Oct 2026 01:00:03 +0000</pubDate>
      <link>https://dev.to/googleai/stop-using-complex-urls-for-your-demosus-central1runapp-2efd</link>
      <guid>https://dev.to/googleai/stop-using-complex-urls-for-your-demosus-central1runapp-2efd</guid>
      <description>&lt;p&gt;Wish you had a custom domain for all of your demos and AI prototypes? When you deploy your apps with Google Cloud Run, you can claim easy-to-remember, globally available subdomains at &lt;strong&gt;zero cost&lt;/strong&gt; (no load balancers required) with &lt;strong&gt;zero DNS configuration&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;With Cloud Run, it’s as easy as &lt;strong&gt;one command&lt;/strong&gt; to turn your code into a live, shareable app without having to manage any infrastructure. With custom URLs, you can create memorable and shareable endpoints in seconds by claiming any available subdomain: my-cool-app.cloud.run.&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%2Fxftyzpiidk98gxwytz61.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%2Fxftyzpiidk98gxwytz61.jpg" alt=" " width="512" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;You won’t have to worry about load balancer setup, DNS records, CNAME setups, or certificate renewals — Cloud Run automates SSL/TLS certificates and global routing for you.&lt;/p&gt;

&lt;p&gt;Deploy your apps on &lt;a href="https://console.cloud.google.com/freetrial?redirectPath=%2Frun&amp;amp;facet_utm_source=%28direct%29&amp;amp;facet_utm_campaign=%28direct%29&amp;amp;facet_utm_medium=%28none%29&amp;amp;facet_url=https%3A%2F%2Fcloud.google.com%2Frun" rel="noopener noreferrer"&gt;Cloud Run&lt;/a&gt; with a custom URL today! &lt;a href="https://cloud.google.com/run/docs/custom-urls" rel="noopener noreferrer"&gt;Read the docs&lt;/a&gt; for more info on getting started.&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>cloud</category>
      <category>software</category>
      <category>programming</category>
    </item>
    <item>
      <title>Activating Your Data Layer for Production-Ready AI</title>
      <dc:creator>Gleb Otochkin</dc:creator>
      <pubDate>Mon, 05 Oct 2026 19:39:51 +0000</pubDate>
      <link>https://dev.to/googleai/activating-your-data-layer-for-production-ready-ai-p3e</link>
      <guid>https://dev.to/googleai/activating-your-data-layer-for-production-ready-ai-p3e</guid>
      <description>&lt;p&gt;When discussing applications and systems using generative AI and the new opportunities they present, one component of the ecosystem is irreplaceable - data. Specifically, the data that companies gather, hold, and use daily. This data serves as the backbone for applications, analytics, knowledge bases, and much more. We use databases to store and work with this data, and most, if not all, AI-driven initiatives and new applications are going to use that data layer.&lt;/p&gt;

&lt;p&gt;But how can we start to use the data in our AI systems? Let me introduce you to some of the labs showing how to prepare and use the data with AI models in Google databases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Semantic Search: Text Embeddings in Database
&lt;/h2&gt;

&lt;p&gt;Our journey starts by preparing our data for semantic search and running first tests to augment the Gen AI model's response by grounding it with your semantic search results. The grounding data is the basis for RAG (Retrieval Augmented Generation). Then, you can improve the performance of your search by indexing your embeddings using the latest indexing techniques.&lt;/p&gt;

&lt;p&gt;One of the options is the &lt;a href="https://cloud.google.com/products/alloydb?e=48754805" rel="noopener noreferrer"&gt;&lt;strong&gt;Google AlloyDB database&lt;/strong&gt;&lt;/a&gt;, which has direct integration with AI models and supports the most demanding workloads. The following lab guides us through all the steps, starting from creating an AlloyDB cluster, loading sample data, and generating embeddings, to using those embeddings to generate an augmented response from the Gen AI model.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  
&lt;h4&gt;
  
  
  Go to the lab!
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Lab&lt;/strong&gt;: &lt;a href="https://codelabs.developers.google.com/alloydb-ai-embedding#0" rel="noopener noreferrer"&gt;Getting started with Vector Embeddings with AlloyDB AI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Objective&lt;/strong&gt;: &lt;em&gt;Create an AlloyDB cluster, generate vector embeddings for sample data, and perform semantic search to ground a Gen AI model's response.&lt;/em&gt;&lt;/p&gt;


&lt;/div&gt;


&lt;p&gt;AI integration is not limited to AlloyDB. All Google Cloud databases have AI integration and are capable of generating and using embeddings for semantic search. For example, if you are using &lt;a href="https://cloud.google.com/sql" rel="noopener noreferrer"&gt;&lt;strong&gt;Cloud SQL&lt;/strong&gt;&lt;/a&gt;, you can also generate and use embeddings for semantic search directly within your existing &lt;a href="https://cloud.google.com/sql/postgresql" rel="noopener noreferrer"&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/a&gt; or &lt;a href="https://cloud.google.com/sql/mysql" rel="noopener noreferrer"&gt;&lt;strong&gt;MySQL&lt;/strong&gt;&lt;/a&gt; instances.&lt;/p&gt;

&lt;p&gt;The next two labs are very similar to the previous one, but instead of Google AlloyDB for PostgreSQL, we are using Cloud SQL for PostgreSQL and Cloud SQL for MySQL to use semantic search as the grounding engine for the model's response. Some steps are of course different due to variations in SQL language and different database engines, but the main idea stays the same: use our data to ground the model response and improve output.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  
&lt;h4&gt;
  
  
  Go to the labs!
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Lab:&lt;/strong&gt; &lt;a href="https://codelabs.developers.google.com/csql-pg-ai-embedding#0" rel="noopener noreferrer"&gt;Getting started with Vector Embeddings in Cloud SQL for PostgreSQL&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Objective:&lt;/strong&gt; &lt;em&gt;Create Cloud SQL for PostgreSQL instance with AI integration, generate vector embeddings for sample data, and perform semantic search to ground a Gen AI model's response.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lab:&lt;/strong&gt; &lt;a href="https://codelabs.developers.google.com/csql-mysql-ai-embedding#0" rel="noopener noreferrer"&gt;Getting started with Vector Embeddings in Cloud SQL for MySQL&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Objective:&lt;/strong&gt; &lt;em&gt;Create Cloud SQL for MySQL instance with AI integration, generate vector embeddings for sample data, and perform semantic search to ground a Gen AI model's response.&lt;/em&gt;&lt;/p&gt;


&lt;/div&gt;


&lt;p&gt;Semantic search using text data is one of the cornerstones and important features making responses much more reliable and useful, but Google Gen AI models can offer much more. Let's talk about multimodal search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multimodal Embeddings: Bring Images to the Search
&lt;/h2&gt;

&lt;p&gt;In real life, of course, we use all our senses, including vision, to evaluate the world around us. The Google multimodal embedding models bring an additional layer of understanding, improving search by using embeddings not only for text but also for images.&lt;/p&gt;

&lt;p&gt;In the following lab, we use a catalog of products placed in AlloyDB and supplemented by images in Google Cloud Storage. In the lab, we show how we can use both text descriptions and images for semantic search, supplementing and replacing each other, naturally incorporating search based on image input into our response.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  
&lt;h4&gt;
  
  
  Go to the lab!
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Lab:&lt;/strong&gt; &lt;a href="https://codelabs.developers.google.com/alloydb-ai-mm-embeddings?hl=en#0" rel="noopener noreferrer"&gt;Multimodal Embeddings in AlloyDB&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Objective:&lt;/strong&gt; &lt;em&gt;Deploy an AlloyDB cluster, import data and use text and image semantic search using multimodal embeddings, try hybrid search approach.&lt;/em&gt;&lt;/p&gt;


&lt;/div&gt;


&lt;p&gt;Preparing the data and making first steps are important for a general understanding of RAG and tools available for the search, but Google has other cases when direct AI integration can help with your data analysis without any data preparations.&lt;/p&gt;

&lt;h2&gt;
  
  
  AlloyDB AI Functions and Reranking
&lt;/h2&gt;

&lt;p&gt;Google AlloyDB database comes with additional AI integrations that help you use some AI capabilities without data preparation. For example, the AI.IF function can perform semantic search on the fly, evaluating sentiment or comparing data in columns with a natural language query, returning results filtered by the query condition. Also, you can apply a ranking function to the search output, improving the final result. You can try some of the new functionality using the following lab and let us know if it can help in your use case.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  
&lt;h4&gt;
  
  
  Go to the lab!
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Lab:&lt;/strong&gt; &lt;a href="https://codelabs.developers.google.com/alloydb-ai-operators?hl=en#0" rel="noopener noreferrer"&gt;AlloyDB AI Operators and Reranking&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Objective:&lt;/strong&gt; &lt;em&gt;Deploy AlloyDB cluster and enable AI query functions, use AI query functions to filter data and ranking function to improve the search results.&lt;/em&gt;&lt;/p&gt;


&lt;/div&gt;


&lt;p&gt;But what if somebody is not particularly savvy with SQL or not familiar with the data structure in your database? The QueryData for AlloyDB can help you with that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate SQL using QueryData for AlloyDB
&lt;/h2&gt;

&lt;p&gt;QueryData lets you interact with the data in your database using conversational language and build data agents. You provide database-specific information in the form of context sets to improve generator accuracy.&lt;/p&gt;

&lt;p&gt;The QueryData context add a layer describing your data schema and providing templates, facets and value search options to make your generated SQL predictable and production ready. The following lab helps you to start with the new features and generate your first queries based on your data schema.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  
&lt;h4&gt;
  
  
  Go to the lab!
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Lab:&lt;/strong&gt; &lt;a href="https://codelabs.developers.google.com/next26/alloydb-querydata?hl=en#0" rel="noopener noreferrer"&gt;QueryData for AlloyDB using Gemini Data Analytics&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Objective:&lt;/strong&gt; &lt;em&gt;Use QueryData for AlloyDB to generate accurate and predictable SQL statements from natural language input in agentic applications&lt;/em&gt;&lt;/p&gt;


&lt;/div&gt;


&lt;h2&gt;
  
  
  From Tests to Production
&lt;/h2&gt;

&lt;p&gt;Those labs are part of the &lt;strong&gt;From Data Foundations to Advanced RAG&lt;/strong&gt; module of our &lt;a href="https://cloud.google.com/blog/topics/developers-practitioners/production-ready-ai-with-google-cloud-learning-path" rel="noopener noreferrer"&gt;Production-Ready AI with Google Cloud&lt;/a&gt; program. Check the other modules and see if they can help you to adopt the AI capabilities provided by our Google Cloud services and tools. The end game goal is a high quality application using the full potential of modern technologies.&lt;/p&gt;

&lt;p&gt;And stay tuned on release notes for ALloyDB and Cloud SQL - the engineering team is busy working on new features and improvements. Happy testing.&lt;/p&gt;

</description>
      <category>database</category>
      <category>googlecloud</category>
      <category>ai</category>
    </item>
    <item>
      <title>Stop "Vibe Checking" Your AI Agents: How to Build Production Evals in 60 Minutes</title>
      <dc:creator>Frank Guan</dc:creator>
      <pubDate>Wed, 30 Sep 2026 16:45:07 +0000</pubDate>
      <link>https://dev.to/googleai/stop-vibe-checking-your-ai-agents-how-to-build-production-evals-in-60-minutes-2df4</link>
      <guid>https://dev.to/googleai/stop-vibe-checking-your-ai-agents-how-to-build-production-evals-in-60-minutes-2df4</guid>
      <description>&lt;p&gt;&lt;a href="https://img.youtube.com/vi/wPdoZRbvaF4/maxresdefault.jpg" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How do you currently test your AI agents?&lt;/p&gt;

&lt;p&gt;If you're like most developers building agentic workflows, you probably rely on the &lt;strong&gt;"vibe check"&lt;/strong&gt;: tweak a prompt, run a couple of manual queries, check if the output looks reasonable, and assume it's good to go.&lt;/p&gt;

&lt;p&gt;Here's why that breaks down fast:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Silent Regressions&lt;/strong&gt;: A teammate submits a PR or tweaks a tool definition, and you have no way to know if performance improved or degraded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost &amp;amp; Latency Spikes&lt;/strong&gt;: An agent gets trapped in a multi-turn retry loop, quietly burning thousands of tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "Looks Right" Trap&lt;/strong&gt;: Output can look perfectly well-written while omitting critical parameters or hallucinating key instructions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this episode of the &lt;strong&gt;AI Agent Clinic&lt;/strong&gt;, Google Cloud engineer Dani Zamora and Matthew Feroz (Merge) tackle this challenge live: &lt;strong&gt;building an end-to-end, framework-agnostic evaluation pipeline in 60 minutes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check out the full walkthrough below:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/wPdoZRbvaF4" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h3&gt;
  
  
  What We Cover in the Video
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Test Subject (DocsHound)&lt;/strong&gt;: Matt brings on DocsHound, an open-source LangGraph agent that scans GitHub issues and PRs to find documentation gaps and open automated PRs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework-Agnostic Telemetry (OpenTelemetry + OpenInference)&lt;/strong&gt;: How to standardize multi-turn traces across any agent framework (LangGraph, CrewAI, AutoGen, ADK) with zero vendor lock-in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 3-Tier Metric Strategy&lt;/strong&gt;: Why you shouldn't start with 50 metrics, and how to combine:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Managed core metrics&lt;/strong&gt;: Trajectory quality, tool-calling correctness, and groundedness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom LLM judges&lt;/strong&gt;: Rubrics tailored to formatting, code block enforcement, and actionability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic assertions &amp;amp; operational tracking&lt;/strong&gt;: Syntax checks, latency, token usage, and API cost.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 0.33 Quality Blind Spot&lt;/strong&gt;: How the automated scorecard caught a 33% documentation quality failure that manual spot-checks completely missed.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Jump Directly to Key Sections
&lt;/h3&gt;

&lt;p&gt;If you want to skip straight to a specific part of the walkthrough:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;00:00&lt;/strong&gt; — Why "vibe checking" fails in production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;02:11&lt;/strong&gt; — Introducing DocsHound (LangGraph agent demo)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;03:40&lt;/strong&gt; — Standardizing traces with OpenTelemetry &amp;amp; OpenInference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;05:51&lt;/strong&gt; — The 60-minute eval challenge begins&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;06:47&lt;/strong&gt; — Step 1: Mapping agent execution flow with Antigravity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;11:18&lt;/strong&gt; — Step 2: Scaffolding the open-source Agent-Eval toolkit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;17:14&lt;/strong&gt; — Balancing quality vs. latency vs. token cost&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;18:34&lt;/strong&gt; — Step 3: Translating quality definitions into custom metrics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;21:40&lt;/strong&gt; — Step 4: Inspecting the scorecard &amp;amp; spotting the 0.33 failure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;24:08&lt;/strong&gt; — Why automated evals change how you build agents&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Open-Source Repositories &amp;amp; Resources
&lt;/h3&gt;

&lt;p&gt;Follow along with the exact tools used in the video:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📺 &lt;strong&gt;Full Video&lt;/strong&gt;: &lt;a href="https://youtu.be/wPdoZRbvaF4" rel="noopener noreferrer"&gt;Watch on YouTube (25 mins)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🛠️ &lt;strong&gt;Agent-Eval Toolkit&lt;/strong&gt;: &lt;a href="https://g.dev/cloud/agent-eval" rel="noopener noreferrer"&gt;github.com/google-cloud-tech/agent-eval&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐶 &lt;strong&gt;DocsHound Agent Code&lt;/strong&gt;: &lt;a href="https://g.dev/cloud/docshound" rel="noopener noreferrer"&gt;github.com/google-cloud-tech/docshound&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📖 &lt;strong&gt;Enterprise Agent Platform Docs&lt;/strong&gt;: &lt;a href="https://g.dev/cloud/agent-evaluation" rel="noopener noreferrer"&gt;g.dev/cloud/agent-evaluation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;strong&gt;Watch the full video above to see how to set up the eval pipeline step-by-step.&lt;/strong&gt; If you have questions about instrumenting your own agent framework or setting up custom metrics, drop a comment below!&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>ai</category>
      <category>python</category>
      <category>langchain</category>
      <category>devops</category>
    </item>
    <item>
      <title>Sub-second semantic filtering in PostgreSQL: Integrating the ultra-fast Jev model with AlloyDB</title>
      <dc:creator>Gleb Otochkin</dc:creator>
      <pubDate>Tue, 29 Sep 2026 17:00:53 +0000</pubDate>
      <link>https://dev.to/googleai/sub-second-semantic-filtering-in-postgresql-integrating-the-ultra-fast-jev-model-with-alloydb-m8k</link>
      <guid>https://dev.to/googleai/sub-second-semantic-filtering-in-postgresql-integrating-the-ultra-fast-jev-model-with-alloydb-m8k</guid>
      <description>&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%2Fq9dbnzqqdjazzymcr8ho.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%2Fq9dbnzqqdjazzymcr8ho.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;If you are working with semantic search, vector embeddings and AI models, you probably already know the process with either building embeddings using direct AI integration like in &lt;a href="https://docs.cloud.google.com/alloydb/docs/ai/work-with-embeddings?utm_campaign=CDR_0x370c34a8_default_b567422655&amp;amp;utm_medium=external&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;AlloyDB&lt;/a&gt; or through application layer and pipelines. Embeddings and direct access to LLM give you options to do classifications, sentiment analysis and many other things.&lt;/p&gt;

&lt;p&gt;But what if you want to run these evaluations directly on your operational data without the latency overhead of generative LLMs? In AlloyDB for PostgreSQL, thanks to &lt;a href="https://docs.cloud.google.com/alloydb/docs/ai/model-endpoint-overview?utm_campaign=CDR_0x370c34a8_default_b567422655&amp;amp;utm_medium=external&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;model endpoint management&lt;/a&gt;, we aren’t restricted only to standard generative models. We can connect specialized, ultra-low-latency models designed specifically for high-speed logical constraints — like &lt;a href="https://typesafe.ai/" rel="noopener noreferrer"&gt;TypeSafe AI&lt;/a&gt;’s Jev model.&lt;/p&gt;

&lt;p&gt;By integrating Jev, which is heavily optimized for returning structural evaluations (like booleans and scores) in milliseconds rather than generating conversational text, we can execute high-throughput semantic queries at database speeds using native functions like ai.if and ai.analyze_sentiment.&lt;/p&gt;

&lt;p&gt;In this post, I will walk you through a complete setup: configuring the cluster and primary instance, adding secrets, creating the necessary PL/pgSQL translation layers, and leveraging Jev’s parallel batching capabilities to see how fast AI evaluations can be without preparing the embeddings in advance. Let’s dive in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Jev model to the cluster
&lt;/h3&gt;

&lt;p&gt;If you already have an AlloyDB cluster you can use it right away or you can create one using &lt;a href="https://docs.cloud.google.com/alloydb/docs/cluster-create?utm_campaign=CDR_0x370c34a8_default_b567422655&amp;amp;utm_medium=external&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;Google documentation&lt;/a&gt;. Make sure you’ve enabled the AI integration following the &lt;a href="https://docs.cloud.google.com/alloydb/docs/ai/configure-vertex-ai?utm_campaign=CDR_0x370c34a8_default_b567422655&amp;amp;utm_medium=external&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;guide&lt;/a&gt; and enabled the following database parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;google_ml_integration.enable_model_support=on&lt;/li&gt;
&lt;li&gt;google_ml_integration.enable_ai_query_engine=on&lt;/li&gt;
&lt;li&gt;google_ml_integration.enable_ai_function_acceleration=on&lt;/li&gt;
&lt;li&gt;google_ml_integration.enable_cost_optimized_ai_functions=on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The combination of all those parameters is what makes AlloyDB AI function to be snappy and give the best performance.&lt;/p&gt;

&lt;p&gt;Also check your primary instance network settings. We need an outbound public IP to be able to reach the Typesafe AI model endpoint. If your instance doesn’t have it you are going to get timeout trying to reach the model.&lt;/p&gt;

&lt;p&gt;If you haven’t already got an API key from &lt;a href="https://typesafe.ai/" rel="noopener noreferrer"&gt;https://typesafe.ai/&lt;/a&gt; then you need to register there and generate the key. It will be used to access the model from AlloyDB.&lt;/p&gt;

&lt;p&gt;The next step is to add the API key to the Google Cloud secret manager. Here is a simple example how to do that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud secrets create typesafe-jev-api-key &lt;span class="nt"&gt;--replication-policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"automatic"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;YOUR_TYPESAFE_API_KEY&amp;gt;"&lt;/span&gt; | gcloud secrets versions add typesafe-jev-api-key &lt;span class="nt"&gt;--data-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it is time to register the model in your database. You can use any of your databases with a sample dataset to test it out. I was using a database with a sample e-commerce dataset with 30 thousand different products.&lt;/p&gt;

&lt;p&gt;In the AlloyDB Studio choose your database and a user who has enough privileges to work with google_ml_integration extension and AI integration functionality. I was doing my tests using ai.if and ai.analyze_sentiment functions with the default model and with the TypeSafe AI’s Jev model.&lt;/p&gt;

&lt;p&gt;TypeSafe AI’s System One API accepts structured JSON with distinct reasoning primitives (Noul for boolean constraints, Choice for multi-class classification). You can read about the primitives in the TypeSafe AI’s &lt;a href="https://docs.typesafe.ai/introduction#typesafe-primitives" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, but here is a very basic explanation:&lt;/p&gt;

&lt;p&gt;You have 3 different question types — Choice, Score or Noul. Depending on the type of the question you get different output. For example, for the type of question Noul you are getting 0 or 1 or, in other words, true or false.&lt;/p&gt;

&lt;p&gt;AlloyDB’s standard ai.* functions need to be translated into this specific format. We use four transformation functions to bridge the gap. Here is the scalar input transform that handles Noul and Choice types for both ai.if and ai.analyze_sentiment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plsql"&gt;&lt;code&gt;&lt;span class="kr"&gt;CREATE&lt;/span&gt; &lt;span class="kr"&gt;OR&lt;/span&gt; &lt;span class="kr"&gt;REPLACE&lt;/span&gt; &lt;span class="kr"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jev_model_input_transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_id&lt;/span&gt; &lt;span class="kt"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;input_text&lt;/span&gt; &lt;span class="k"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;generation_config&lt;/span&gt; &lt;span class="k"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;system_instruction&lt;/span&gt; &lt;span class="k"&gt;TEXT&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;JSON&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt; &lt;span class="k"&gt;IMMUTABLE&lt;/span&gt; &lt;span class="kr"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt;
    &lt;span class="n"&gt;full_instruction&lt;/span&gt; &lt;span class="k"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;enum_json&lt;/span&gt; &lt;span class="k"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;criteria_obj&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{}&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;elem&lt;/span&gt; &lt;span class="k"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;q_obj&lt;/span&gt; &lt;span class="k"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="c1"&gt;-- Combine optional system instructions with the input prompt&lt;/span&gt;
    &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;system_instruction&lt;/span&gt; &lt;span class="ow"&gt;IS&lt;/span&gt; &lt;span class="ow"&gt;NOT&lt;/span&gt; &lt;span class="ow"&gt;NULL&lt;/span&gt; &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_instruction&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="kr"&gt;THEN&lt;/span&gt;
        &lt;span class="n"&gt;full_instruction&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;system_instruction&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;input_text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;ELSE&lt;/span&gt;
        &lt;span class="n"&gt;full_instruction&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;input_text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;-- Inspect generation_config to determine whether caller expects a multi-class choice or boolean&lt;/span&gt;
    &lt;span class="n"&gt;enum_json&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nf"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;generation_config&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;'&lt;/span&gt;&lt;span class="s1"&gt;generationConfig&lt;/span&gt;&lt;span class="o"&gt;'-&amp;gt;'&lt;/span&gt;&lt;span class="s1"&gt;responseSchema&lt;/span&gt;&lt;span class="o"&gt;'-&amp;gt;'&lt;/span&gt;&lt;span class="s1"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;generation_config&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;'&lt;/span&gt;&lt;span class="s1"&gt;responseSchema&lt;/span&gt;&lt;span class="o"&gt;'-&amp;gt;'&lt;/span&gt;&lt;span class="s1"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;enum_json&lt;/span&gt; &lt;span class="ow"&gt;IS&lt;/span&gt; &lt;span class="ow"&gt;NOT&lt;/span&gt; &lt;span class="ow"&gt;NULL&lt;/span&gt; &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;json_typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt; &lt;span class="kr"&gt;THEN&lt;/span&gt;
        &lt;span class="c1"&gt;-- Multi-class classification (ai.analyze_sentiment) -&amp;gt; Jev choice primitive&lt;/span&gt;
        &lt;span class="kr"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="kr"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;json_array_elements_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;LOOP&lt;/span&gt;
            &lt;span class="n"&gt;criteria_obj&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;criteria_obj&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;jsonb_build_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Category: &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="kr"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;q_obj&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json_build_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;type&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;choice&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_instruction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;criteria&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;criteria_obj&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;JSON&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kr"&gt;ELSE&lt;/span&gt;
        &lt;span class="c1"&gt;-- Boolean filtering (ai.if) -&amp;gt; Jev noul primitive&lt;/span&gt;
        &lt;span class="n"&gt;q_obj&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json_build_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;type&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;noul&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_instruction&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;IF&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;json_build_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;model&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jev-latest&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;state&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generation_config&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;'&lt;/span&gt;&lt;span class="s1"&gt;state&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Evaluate the question accurately based on the provided text.&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;questions&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json_build_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;q_1&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;q_obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need a scalar output transform to unpack Jev’s JSON response back into a PostgreSQL TEXT result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plsql"&gt;&lt;code&gt;&lt;span class="kr"&gt;CREATE&lt;/span&gt; &lt;span class="kr"&gt;OR&lt;/span&gt; &lt;span class="kr"&gt;REPLACE&lt;/span&gt; &lt;span class="kr"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jev_model_output_transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_id&lt;/span&gt; &lt;span class="kt"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;response_json&lt;/span&gt; &lt;span class="k"&gt;JSON&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TEXT&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt; &lt;span class="k"&gt;IMMUTABLE&lt;/span&gt; &lt;span class="kr"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt;
    &lt;span class="n"&gt;q_ans&lt;/span&gt; &lt;span class="k"&gt;JSON&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;response_json&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;'&lt;/span&gt;&lt;span class="s1"&gt;answers&lt;/span&gt;&lt;span class="o"&gt;'-&amp;gt;'&lt;/span&gt;&lt;span class="s1"&gt;q_1&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;ans_type&lt;/span&gt; &lt;span class="k"&gt;TEXT&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;q_ans&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;'&lt;/span&gt;&lt;span class="s1"&gt;type&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;ans_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;choice&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt; &lt;span class="kr"&gt;THEN&lt;/span&gt;
        &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;q_ans&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;'&lt;/span&gt;&lt;span class="s1"&gt;choice&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;ELSIF&lt;/span&gt; &lt;span class="n"&gt;ans_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;noul&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt; &lt;span class="kr"&gt;THEN&lt;/span&gt;
        &lt;span class="c1"&gt;-- Convert probability score (0.00 to 1.00) to boolean text&lt;/span&gt;
        &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q_ans&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;'&lt;/span&gt;&lt;span class="s1"&gt;noul&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="kt"&gt;FLOAT&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.50&lt;/span&gt; &lt;span class="kr"&gt;THEN&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt; &lt;span class="kr"&gt;ELSE&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;false&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;ELSE&lt;/span&gt;
        &lt;span class="kr"&gt;RAISE&lt;/span&gt; &lt;span class="k"&gt;EXCEPTION&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unexpected Jev response payload: %&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a batched input and output you should use a modified set of functions which understand input and output as an array of values. All the functions are available in &lt;a href="https://codelabs.developers.google.com/alloydb-ai-jev?utm_campaign=CDR_0x370c34a8_default_b567422655&amp;amp;utm_medium=external&amp;amp;utm_source=blog#5" rel="noopener noreferrer"&gt;the codelab&lt;/a&gt; created by my colleague Paul Ramsey.&lt;/p&gt;

&lt;p&gt;Now we bind the transformation functions, secret credential, and the external REST endpoint together:&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;CALL&lt;/span&gt; &lt;span class="n"&gt;google_ml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'jev-model'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_request_url&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'https://api.typesafe.ai/v1/systemone'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_provider&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'custom'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_type&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'llm'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_qualified_name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'jev-latest'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_auth_type&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'secret_manager'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_auth_id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'jev_api_key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_in_transform_fn&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'jev_model_input_transform'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_out_transform_fn&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'jev_model_output_transform'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_batch_in_transform_fn&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'jev_model_batch_input_transform'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_batch_out_transform_fn&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'jev_model_batch_output_transform'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ve noticed the model is using the Google secret manager as authentication provider with the auth key as the secret name.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Semantic Testing
&lt;/h3&gt;

&lt;p&gt;With jev-model registered, we can execute semantic operations directly inside standard PostgreSQL queries.&lt;/p&gt;

&lt;p&gt;First, let’s test a simple boolean constraint using ai.if:&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="n"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;if&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;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Is the product "North Face Waterproof Gore-Tex Hiking Jacket ($249)" suitable for rainy outdoor conditions?'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'jev-model'&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;is_suitable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And getting expected result in just few millliseconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; is_suitable
-------------
 true
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let’s test multi-class classification using ai.analyze_sentiment on a customer review:&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="n"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;analyze_sentiment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'The stitching on this jacket tore on the second day and the zipper jammed completely.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'jev-model'&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;sentiment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the result in ms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; sentiment
-----------
 negative
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  High-Throughput Array Batching on Tables
&lt;/h3&gt;

&lt;p&gt;Usually, executing an HTTP call for thousands of rows row-by-row is painfully slow. However, by using PostgreSQL’s row_number() window function and array batching, we can evaluate dozens of items in a single HTTP round-trip without generative degradation.&lt;br&gt;&lt;br&gt;
Here is an example evaluating a 50-row batch of real catalog items to find cold-weather gear:&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;EXPLAIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BUFFERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;numbered&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; 
        &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product_description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;row_number&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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="mi"&gt;50&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;batch_id&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&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;id&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
    &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;batched&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; 
        &lt;span class="n"&gt;batch_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;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;id&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;ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;prompts&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;array_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Is "'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'" designed for cold weather winter outerwear?'&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;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;model_id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'jev-model'&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;decisions&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;numbered&lt;/span&gt;
    &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;batch_id&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;unrolled&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; 
        &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;batch_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;cold_weather_gear&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;batched&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;CROSS&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="k"&gt;LATERAL&lt;/span&gt; &lt;span class="k"&gt;unnest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decisions&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;u&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cold_weather_gear&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;unrolled&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;cold_weather_gear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are the results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  id | name | cold_weather_gear
-------+--------------------------------------------------------------+-------------------
 29071 | Winter Striped Beanie with Pom | true
 29077 | Knit Collegiate Rugby Stripe Winter Scarf &amp;amp; Beanie Hat Set | true
 29083 | Junction Finds Men's Muscle Wool Blend Trapper Hat | true
 29086 | Mens Soft Thermal Insulated Wrist Length Leather Gloves | true
 29090 | Long Beanie-Red W16S24E | true
(5 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is the execution plan for the query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                                                                              QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit (cost=6515.00..506515.19 rows=5 width=41) (actual time=369.386..369.391 rows=5.00 loops=1)
   Buffers: shared hit=2479
   -&amp;gt; Nested Loop (cost=6515.00..25006524.75 rows=250 width=41) (actual time=369.385..369.389 rows=5.00 loops=1)
         Buffers: shared hit=2479
         -&amp;gt; GroupAggregate (cost=6514.99..25006516.74 rows=50 width=104) (actual time=369.368..369.370 rows=1.00 loops=1)
               Group Key: numbered.batch_id
               Buffers: shared hit=2479
               -&amp;gt; Sort (cost=6514.99..6515.11 rows=50 width=67) (actual time=17.838..17.840 rows=31.00 loops=1)
                     Sort Key: numbered.batch_id, numbered.id
                     Sort Method: quicksort Memory: 29kB
                     Buffers: shared hit=2440
                     -&amp;gt; Subquery Scan on numbered (cost=6512.95..6513.58 rows=50 width=67) (actual time=17.819..17.826 rows=50.00 loops=1)
                           Buffers: shared hit=2440
                           -&amp;gt; Limit (cost=6512.95..6513.08 rows=50 width=615) (actual time=17.816..17.819 rows=50.00 loops=1)
                                 Buffers: shared hit=2440
                                 -&amp;gt; Sort (cost=6512.95..6585.75 rows=29120 width=615) (actual time=17.815..17.817 rows=50.00 loops=1)
                                       Sort Key: products.id DESC
                                       Sort Method: top-N heapsort Memory: 37kB
                                       Buffers: shared hit=2440
                                       -&amp;gt; WindowAgg (cost=4890.43..5545.61 rows=29120 width=615) (actual time=8.715..14.386 rows=29120.00 loops=1)
                                             Window: w1 AS (ORDER BY products.id ROWS UNBOUNDED PRECEDING)
                                             Storage: Memory Maximum Storage: 17kB
                                             Buffers: shared hit=2440
                                             -&amp;gt; Sort (cost=4890.41..4963.21 rows=29120 width=59) (actual time=8.700..9.204 rows=29120.00 loops=1)
                                                   Sort Key: products.id
                                                   Sort Method: quicksort Memory: 3014kB
                                                   Buffers: shared hit=2440
                                                   -&amp;gt; Seq Scan on products (cost=0.00..2731.20 rows=29120 width=59) (actual time=0.014..3.791 rows=29120.00 loops=1)
                                                         Buffers: shared hit=2440
         -&amp;gt; Function Scan on u (cost=0.01..0.11 rows=5 width=41) (actual time=0.013..0.015 rows=5.00 loops=1)
               Filter: decision
               Rows Removed by Filter: 15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The execution plan shows that PostgreSQL sorts and caps the dataset to 50 rows, aggregates their prompt strings into an array within GroupAggregate, and invokes ai.if once. This consolidates 50 model evaluations into a single network payload — taking ~351 ms — before unnesting and filtering the results, drastically reducing HTTP roundtrips and connection overhead compared to row-by-row execution.&lt;/p&gt;

&lt;p&gt;The execution time was consistently around 180 ms. You can compare it with a default model and non-batched approach. My tests for the non-batched approach showed around 5600 ms using the same Jev model and 19500 ms with the default model. So the batching improves performance more than 30 times.&lt;/p&gt;

&lt;p&gt;The same approach worked for the ai.analyze_sentiment function where I was taking user’s reviews and passing multiple reviews as arguments to the function capturing the output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;Comparing the traditional ETL-to-AI pipeline with AlloyDB’s native Model Endpoint Management shows massive improvements in architectural simplicity and performance. It worked really well with incredible speed.&lt;/p&gt;

&lt;p&gt;Here are some thoughts after testing the TypeSafe AI’s Jev directly in AlloyDB:&lt;/p&gt;

&lt;p&gt;Model Speed: Generative LLMs are relatively slow because they stream text token-by-token. Jev bypasses this entirely, returning structured logical primitives (like noul and score) in milliseconds, making it uniquely suited for synchronous database operations.&lt;/p&gt;

&lt;p&gt;Flexibility: You are not locked into one provider. You can securely authenticate with Secret Manager and use custom SQL transforms to adapt standard ai.* functions to any external REST API format.&lt;/p&gt;

&lt;p&gt;Database Performance: Using PostgreSQL window functions and array batching maps perfectly to Jev’s parallel API design, allowing you to process dozens of questions simultaneously inside a single HTTP round-trip. The database optimizer combines data before calling the AI model, minimizing overhead.&lt;/p&gt;

&lt;p&gt;You can test the presented scenario with an e-commerce dataset using the &lt;a href="https://codelabs.developers.google.com/alloydb-ai-jev?utm_campaign=CDR_0x370c34a8_default_b567422655&amp;amp;utm_medium=external&amp;amp;utm_source=blog#0" rel="noopener noreferrer"&gt;published codelab&lt;/a&gt; and maybe expand your testing comparing the performance and doing sentiment scoring.&lt;/p&gt;

&lt;p&gt;Happy testing!&lt;/p&gt;




</description>
      <category>typesafeai</category>
      <category>alloydbai</category>
      <category>performance</category>
      <category>postgres</category>
    </item>
    <item>
      <title>How to Build a Real-Time Voice AI Agent with the Gemini Live API</title>
      <dc:creator>Annie Cusack</dc:creator>
      <pubDate>Mon, 28 Sep 2026 22:22:04 +0000</pubDate>
      <link>https://dev.to/googleai/how-to-build-a-real-time-voice-ai-agent-with-the-gemini-live-api-1dhe</link>
      <guid>https://dev.to/googleai/how-to-build-a-real-time-voice-ai-agent-with-the-gemini-live-api-1dhe</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally &lt;a href="https://x.com/GoogleCloudTech/status/2102773125960134804" rel="noopener noreferrer"&gt;posted on X&lt;/a&gt;, by Annie Wang, Developer Relations Engineer, Google Cloud and Annie Cusack, Developer Marketing Manager, Google Cloud&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most of us talk to AI assistants throughout our day. When you want to listen to a specific song, you probably just use your voice instead of manually typing on your phone or touching buttons on a speaker.&lt;/p&gt;

&lt;p&gt;But there is a big difference between that experience and a live voice agent. It all comes down to a natural conversational dynamic.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
Me: "Hey, Mira, can you play something dream pop?"&lt;br&gt;
Live voice: "That sounds perfect."&lt;br&gt;
Me: [Interrupts] "Skip this one."&lt;br&gt;
Live voice: "Skipping it for you."&lt;/p&gt;

&lt;p&gt;That is a live voice agent. It listens, it answers in real time, and you can interrupt it mid-sentence, just like a real phone call. Most AI voice systems can't do this. Here’s how to build an agent that can, using the Gemini Live API.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/yQEKMsCtsmE" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Text-to-speech vs. Gemini Live (one direction vs. two)
&lt;/h2&gt;

&lt;p&gt;Before we build, it’s helpful to ground ourselves in some context. Most AI assistants today use Text-to-Speech (TTS). TTS is simple in that you give it text and it sends back an audio response. In this case, it can speak, but it can’t “hear you” as a human would.&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%2Fmnuou4itnt254wrmlh06.jpeg" 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%2Fmnuou4itnt254wrmlh06.jpeg" alt="How text to speech works in Gemini Live" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gemini Live uses audio-to-audio, which communicates both directions at the same time. It hears your voice as raw audio and speaks back as raw audio. Because it is audio-native, the model can perceive tone, pauses, energy, and inflection. It even starts speaking while it is still forming the answer, instead of waiting for the entire text response to finish.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture and the Core Loop
&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%2Fku9fhzqeag5oqoby0jdj.jpeg" 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%2Fku9fhzqeag5oqoby0jdj.jpeg" alt="Graphic showing how the Gemini Live API works end to end" width="680" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To build with the Gemini Live API, there are three pieces you need to connect:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Browser client: Captures your microphone audio and plays a response.&lt;/li&gt;
&lt;li&gt;Gemini Live API: The audio-native model that processes live audio, detects speech, and generates voice.&lt;/li&gt;
&lt;li&gt;Python backend: A lightweight relay that holds a persistent connection to Gemini and passes audio via the google-genai SDK.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The browser and backend talk over a WebSocket. A normal web request asks once and closes. A WebSocket stays open. That persistent connection allows audio to flow continuously in both directions at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Open the bidirectional WebSocket connection
&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%2Fb3rrqsx6gn1t9fsriu3g.jpeg" 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%2Fb3rrqsx6gn1t9fsriu3g.jpeg" alt="Architecture diagram" width="680" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A standard HTTP request asks once and closes. A live voice call needs an open channel where audio travels both ways simultaneously: streaming your microphone audio to Gemini, and receiving Gemini's voice back down.&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%2Futjxbbl4eaps0x8puhvz.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%2Futjxbbl4eaps0x8puhvz.png" alt="Table showing action, protocol, implementation" width="799" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Implement the 4-Step Core Loop
&lt;/h2&gt;

&lt;p&gt;Once the connection is open, your backend runs two loops at the same time, not one after the other. One pumps microphone audio up to Gemini. The other pulls voice chunks down and plays them. Neither waits for the other to finish. That concurrency is what makes the conversation feel live, and it is what makes interruption possible at all.&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%2Fspdutzla543mku1x1nkv.jpeg" 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%2Fspdutzla543mku1x1nkv.jpeg" alt="Open, send, receive, play diagram" width="680" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Four operations that run continuously:&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%2Fxg1yk38lueg11qhvrhr4.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%2Fxg1yk38lueg11qhvrhr4.png" alt="Table showing step, operation, and what the code does" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Reduce latency with the barge-in trick
&lt;/h2&gt;

&lt;p&gt;Interruption is the hardest part of the whole build, and it is the part that decides whether the agent feels alive or feels like a phone tree. The problem is that the model can only tell you it has been interrupted after your voice has crossed the network, been processed, and a signal has come back. By then the agent has been talking over you for a few hundred milliseconds, and the illusion is gone.&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%2F5azs1wel0ccgo3jg7xod.jpeg" 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%2F5azs1wel0ccgo3jg7xod.jpeg" alt="Diagram showing how barge-in trick reduces latency" width="680" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make the agent’s response feel like a natural interaction, the model needs to know when you start talking and when you stop. This is called voice activity detection (VAD) and it’s built into Gemini Live.&lt;/p&gt;

&lt;p&gt;When the built-in VAD hears you speak while the model is outputting audio, the model halts generation and sends an interrupted signal. Because VAD is handled automatically on the server, your client needs to stream audio even when you are quiet so the model has a steady baseline to catch the exact millisecond you begin speaking.&lt;/p&gt;

&lt;p&gt;This same VAD is what powers barge-in, which helps the agent stop instantly the moment someone starts talking over it.&lt;/p&gt;

&lt;p&gt;Although, waiting for that signal across the network creates a delay. To make interruption feel truly instant, you need to mute and clear your local playback buffer in the browser the exact millisecond your microphone hears you speak. Do not wait for the server-side interruption signal to travel back across the network. Cut local audio first, and let the model catch up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Add tools to give your agent capabilities
&lt;/h2&gt;

&lt;p&gt;On its own, a voice model can only produce words. It can’t do things like play a playlist, skip a track, or pause music. To give your agent those types of capabilities, you need to attach tools.&lt;/p&gt;

&lt;p&gt;A tool is a function in your code with a name, description, and parameters. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;play_playlist(genre)&lt;/li&gt;
&lt;li&gt;skip_track()&lt;/li&gt;
&lt;li&gt;pause_music()&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the model decides an action is required, it outputs a tool call containing the parameters it extracted from your voice. Your backend code executes the function and reports the result back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But voice interactions have a strict constraint:&lt;/strong&gt; Slow tool = silent agent.&lt;/p&gt;

&lt;p&gt;While a tool is running, the model pauses voice generation to wait for the result. If a tool takes three seconds to complete an external API request, your agent stops talking, which creates an awkward pause.&lt;/p&gt;

&lt;p&gt;To keep the conversation flowing, implement the asynchronous fire-and-acknowledge pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a tool like play_playlist is triggered, dispatch the slow playback action asynchronously.&lt;/li&gt;
&lt;li&gt;Immediately return an instant confirmation code or receipt (e.g., {"status": "success"}) back to the model.&lt;/li&gt;
&lt;li&gt;The model receives the instant success receipt and continues talking seamlessly (i.e. "Skipping it for you"), while the actual music begins playing in the background.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dive into the code and try for yourself
&lt;/h2&gt;

&lt;p&gt;Building a real-time voice agent with the raw Gemini Live API unlocks low-latency, audio-native capabilities that standard chained TTS pipelines can’t replicate, and it helps you create voice agents that interact in a natural, conversational way.&lt;/p&gt;

&lt;p&gt;See the full code and run the demo yourself. Find the complete implementation for Mira, our late-night radio DJ voice agent, in this &lt;a href="https://github.com/cuppibla/live-dj" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>voiceai</category>
      <category>ai</category>
      <category>googlecloud</category>
      <category>gemini</category>
    </item>
    <item>
      <title>7 ways to lock down AI agent sandboxes in production (beyond Docker containers)</title>
      <dc:creator>Praveen Rajasekar</dc:creator>
      <pubDate>Mon, 28 Sep 2026 22:17:28 +0000</pubDate>
      <link>https://dev.to/googleai/7-ways-to-lock-down-ai-agent-sandboxes-in-production-beyond-docker-containers-2bg3</link>
      <guid>https://dev.to/googleai/7-ways-to-lock-down-ai-agent-sandboxes-in-production-beyond-docker-containers-2bg3</guid>
      <description>&lt;p&gt;Running an autonomous coding or ops agent directly on your laptop feels like a superpower for the first few minutes. It reads your local repository, runs bash commands in your terminal, and fixes a failing test while you grab a drink.&lt;/p&gt;

&lt;p&gt;Then reality sets in. A hallucinated shell command wipes a local folder, a dependency script hangs your machine, or an indirect prompt injection hidden inside a GitHub issue quietly reads your ~/.ssh keys and cloud credentials. And simply wrapping the agent in a standard docker run container does not solve it: standard containers share your host machine's operating system kernel, inherit whatever API keys you pass into them, and cannot stop a tricked process from leaking data.&lt;/p&gt;

&lt;p&gt;If you are moving autonomous agents off your laptops into production cloud environments, here are 7 practical ways to lock down your agent sandboxes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.  Use real kernel isolation (gVisor or microVMs), not standard Docker.&lt;/strong&gt; Standard Docker containers isolate processes on the surface, but every container shares the underlying host operating system kernel. If an agent runs untrusted code that triggers a kernel bug, it can break out onto the host server. &lt;/p&gt;

&lt;p&gt;In production, choose between two stronger isolation options based on your workload:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;User-space kernel sandboxes (gVisor):&lt;/em&gt; Used by &lt;a href="https://docs.cloud.google.com/run/docs/container-contract" rel="noopener noreferrer"&gt;Cloud Run&lt;/a&gt; and &lt;a href="https://docs.cloud.google.com/kubernetes-engine/docs/concepts/sandbox-pods" rel="noopener noreferrer"&gt;GKE Sandbox&lt;/a&gt;. gVisor acts like a security checkpoint proxy between the container and the host OS, inspecting every file and network request before it ever touches the real hardware. It starts in milliseconds and works with normal container images, though tasks that create thousands of tiny files (like npm install or large git checkouts) run slightly slower because the proxy inspects every single file operation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Lightweight microVMs (Firecracker / KVM):&lt;/em&gt; Instead of inspecting requests through a proxy, a microVM uses hardware virtualization to boot a tiny, separate Linux kernel for each agent in about 125 milliseconds. Pick microVMs (or &lt;a href="https://cloud.google.com/security/products/confidential-computing" rel="noopener noreferrer"&gt;Compute Engine Confidential VMs&lt;/a&gt;) when your agent needs full sudo/root permissions or runs heavy builds. If you just need a managed sandbox to run model-generated Python or bash scripts without managing servers, use the &lt;a href="https://docs.cloud.google.com/gemini-enterprise-agent-platform/scale/sandbox/code-execution-overview" rel="noopener noreferrer"&gt;Gemini Enterprise Code Execution Sandbox&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Keep raw API keys and secrets outside the sandbox.&lt;/strong&gt; A common mistake is grabbing a temporary 10-minute token from a secret manager and passing it into the agent's container as an environment variable (process.env). To a prompt-injected agent, 10 minutes is an eternity, a single curl command can steal and send that key to an external server in 200 milliseconds. Never put real credentials inside the container. Keep your keys stored safely in &lt;a href="https://cloud.google.com/security/products/secret-manager" rel="noopener noreferrer"&gt;Google Cloud Secret Manager&lt;/a&gt;, and route the agent's outbound API calls through an external gateway proxy like &lt;a href="https://cloud.google.com/solutions/apigee-ai" rel="noopener noreferrer"&gt;Apigee AI Gateway&lt;/a&gt;. The gateway authenticates the agent via &lt;a href="https://docs.cloud.google.com/iam/docs/workload-identity-federation" rel="noopener noreferrer"&gt;IAM Workload Identity&lt;/a&gt; and attaches the real API key on the way out only when the agent is calling an approved destination URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Lock down outbound network access across every step.&lt;/strong&gt; Recent &lt;a href="https://workos.com/blog/agent-sandbox-egress-defaults" rel="noopener noreferrer"&gt;security audits of cloud coding agents&lt;/a&gt; found two common blind spots:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Setup script leak:&lt;/em&gt; Many platforms block internet access while the agent is reasoning, but leave the internet wide open during the initial npm install or pip install setup step, allowing a malicious package script in a repository to steal data before the agent even starts working.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Bash only trap:&lt;/em&gt; Sandboxing the bash terminal tool while letting file-reading tools, browser tools, or &lt;a href="https://modelcontextprotocol.io/docs/2026-07-28/getting-started/intro" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; servers run unrestricted on the host machine. Block outbound internet access by default across the entire lifecycle (&lt;a href="https://cloud.google.com/security/vpc-service-controls" rel="noopener noreferrer"&gt;VPC Service Controls&lt;/a&gt; and egress firewall rules). Pull code packages from a private, pre-scanned cache (&lt;a href="//cloud.google.com/artifact-registry"&gt;Artifact Registry&lt;/a&gt;) instead of the open web, and only allow the sandbox to talk to an explicit list of approved domains.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Track cumulative actions across turns, not just single tool calls.&lt;/strong&gt; Pre-execution hooks (like &lt;a href="https://antigravity.google/docs/hooks/" rel="noopener noreferrer"&gt;Antigravity PreToolUse hooks&lt;/a&gt; and &lt;a href="https://adk.dev/2.0/" rel="noopener noreferrer"&gt;ADK 2.0&lt;/a&gt; tool callbacks) are essential for checking one command at a time, such as blocking rm -rf or enforcing a $50 limit on a refund tool. But single-step rules have no memory. As Google Cloud's engineering team &lt;a href="https://developers.googleblog.com/build-zero-trust-ai-agents-that-judge-intent-not-just-syntax/" rel="noopener noreferrer"&gt;recently demonstrated&lt;/a&gt;, a tricked agent will bypass a $50 limit simply by calling issue_refund($20) six times in a loop to drain $120. Pair single-step parameter checks with session-level budgets and anomaly detection at the gateway (&lt;a href="https://docs.cloud.google.com/model-armor/model-armor-apigee-integration" rel="noopener noreferrer"&gt;Model Armor&lt;/a&gt;) so your system tracks the total impact across the whole conversation and stops the loop when behavior drifts off course.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Run file edits in isolated workspaces and protect CI/test files.&lt;/strong&gt; If a coding agent can edit its own unit tests or CI/CD pipeline files (.github/workflows, cloudbuild.yaml, .git/hooks), it might "pass" a failing test simply by deleting the test assertion or slip a command into the build config that runs outside the sandbox when a human reviews the pull request. Give the agent an isolated copy of the workspace (like the isolated git worktrees in &lt;a href="https://antigravity.google/" rel="noopener noreferrer"&gt;Google Antigravity&lt;/a&gt;), mount your test suites and CI configuration folders as read-only, and automatically block any pull request where the agent tried to modify build pipelines or security rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Avoid paying for idle compute while waiting on the LLM.&lt;/strong&gt; An AI agent uses compute very differently than a normal web server. During a 10-minute debugging session, the sandbox might use 100% CPU for 2 seconds to run a test, and then sit 70% to 85% of the time doing nothing while waiting for the LLM to stream tokens back or waiting for human approval (&lt;a href="https://upstash.com/blog/ai-agent-sandbox-providers-compared-2026" rel="noopener noreferrer"&gt;Upstash 2026 Sandbox Benchmark&lt;/a&gt;). If you run agent sandboxes on standard 24/7 virtual machines, you pay 5x for idle CPU and memory. Use serverless runtimes with active-CPU billing like &lt;a href="https://cloud.google.com/run" rel="noopener noreferrer"&gt;Cloud Run&lt;/a&gt;, where you pay for CPU only during the seconds a command is actively running and save session progress externally in &lt;a href="https://docs.cloud.google.com/gemini-enterprise-agent-platform/scale/sessions" rel="noopener noreferrer"&gt;Gemini Enterprise Agent Platform Sessions&lt;/a&gt; so idle sandboxes can shut down between turns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Send audit logs outside the sandbox in real time.&lt;/strong&gt; If you store your execution logs inside the same container where the agent has terminal access, a crashed container or a rogue command can wipe the logs before you ever see them. Stream your telemetry directly from the outside host into &lt;a href="https://docs.cloud.google.com/gemini-enterprise-agent-platform/scale/runtime/tracing" rel="noopener noreferrer"&gt;Cloud Trace&lt;/a&gt; and &lt;a href="//cloud.google.com/logging/docs/audit"&gt;Cloud Audit Logs&lt;/a&gt; as the agent runs. For every turn, record four simple things under a single run ID: &lt;br&gt;
1) what the agent asked to do, &lt;br&gt;
2) whether your security rules allowed or blocked it, &lt;br&gt;
3) what files or network URLs were actually touched, and &lt;br&gt;
4) confirmation that the temporary container and its permissions were wiped clean when the task finished.&lt;/p&gt;

&lt;p&gt;We shouldn't let probabilistic agents run unquarantined on our laptops or shared kernel containers. Isolate the kernel with gVisor or microVMs, keep real API keys outside the container, lock down setup-script internet access, and enforce budgets across the entire multi-turn loop.&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%2Fyxibo77064fl167iyfkp.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%2Fyxibo77064fl167iyfkp.jpg" alt="Summary of how to lock down AI agent sandbox in production" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How are you isolating your AI agents in production today? gVisor, microVMs, or external gateway proxies? Let's discuss in the comments below! &lt;/p&gt;

</description>
      <category>agents</category>
      <category>sandbox</category>
      <category>ai</category>
    </item>
    <item>
      <title>Claude Sonnet 5.5 is now available on Google Cloud</title>
      <dc:creator>Jen Harvey</dc:creator>
      <pubDate>Mon, 28 Sep 2026 18:53:36 +0000</pubDate>
      <link>https://dev.to/googleai/claude-sonnet-55-is-now-available-on-google-cloud-168h</link>
      <guid>https://dev.to/googleai/claude-sonnet-55-is-now-available-on-google-cloud-168h</guid>
      <description>&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%2Fkxjjtstcgjw16j9u1yny.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%2Fkxjjtstcgjw16j9u1yny.jpg" alt="Image showing Claude Sonnet 5.5 is now on Google Cloud with a rainbow colored visual in the background" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Designed for coding and knowledge work, it helps you build features, and create clean work materials quicker with a lower cost-per-task at faster speed.&lt;/p&gt;

&lt;p&gt;👉 Try it today: [&lt;a href="https://console.cloud.google.com/agent-platform/publishers/anthropic/model-garden/claude-sonnet-5-5" rel="noopener noreferrer"&gt;https://console.cloud.google.com/agent-platform/publishers/anthropic/model-garden/claude-sonnet-5-5&lt;/a&gt;]&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Claude on Google Cloud workshop | NYC | 9/30</title>
      <dc:creator>Jen Harvey</dc:creator>
      <pubDate>Fri, 25 Sep 2026 11:50:46 +0000</pubDate>
      <link>https://dev.to/googleai/claude-on-google-cloud-workshop-nyc-930-17jl</link>
      <guid>https://dev.to/googleai/claude-on-google-cloud-workshop-nyc-930-17jl</guid>
      <description>&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%2Fu7oynqdkkl3mje5y2r96.jpeg" 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%2Fu7oynqdkkl3mje5y2r96.jpeg" alt="A graphic showing an outline of a human holding four shapes" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On Wednesday, September 30, Anthropic and Google Cloud are co-hosting a hands-on developer workshop in New York City. It runs from 8:30am to 12:30pm at the Google office, and is designed for engineers and technical leaders who want to get past evaluating AI coding tools and into actually using them. &lt;/p&gt;

&lt;p&gt;The morning covers Claude Code and Claude Desktop on Google Cloud. Most of the time is spent in a hands-on lab session, and participants leave with a working setup. Anthropic and Google Cloud engineers will be on-site throughout to answer technical questions.&lt;/p&gt;

&lt;p&gt;During the session, you will have the opportunity to: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;See how Claude fits into your existing workflows across the software development lifecycle&lt;/li&gt;
&lt;li&gt;Learn best practices around integrating Claude into your Google Cloud environment&lt;/li&gt;
&lt;li&gt;Understand how to build on your existing security and compliance requirements&lt;/li&gt;
&lt;li&gt;Work through guided labs with Anthropic and Google Cloud experts available throughout&lt;/li&gt;
&lt;li&gt;Walk away with a working setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://anthropic.swoogo.com/claude-google-cloud-workshop-new-york-30-september/register" rel="noopener noreferrer"&gt;RSVP today&lt;/a&gt;&lt;/p&gt;

</description>
      <category>eventsinyourcity</category>
      <category>claude</category>
      <category>googlecloud</category>
      <category>ai</category>
    </item>
    <item>
      <title>Inline vs. Separate Tables for Vectors in Postgres: Measuring the Join Overhead</title>
      <dc:creator>Gleb Otochkin</dc:creator>
      <pubDate>Fri, 25 Sep 2026 04:29:18 +0000</pubDate>
      <link>https://dev.to/googleai/inline-vs-separate-tables-for-vectors-in-postgres-measuring-the-join-overhead-4f2b</link>
      <guid>https://dev.to/googleai/inline-vs-separate-tables-for-vectors-in-postgres-measuring-the-join-overhead-4f2b</guid>
      <description>&lt;p&gt;Testing semantic search, filtered queries, and multi-table joins in AlloyDB to measure the true cost of decoupling your vectors.&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%2Fjfnktytr1a3cf2jql78s.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%2Fjfnktytr1a3cf2jql78s.png" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;If you are working with vector embeddings you probably already know the embedding models are evolving and you need to refresh your embeddings with a new version from time to time. In the &lt;a href="https://medium.com/google-cloud/ba6edb33e280" rel="noopener noreferrer"&gt;last post&lt;/a&gt; we discussed the bloating problem in the tables, TOAST segments and indexes related to the embeddings refresh. As an alternative to storing embeddings in the same table with your data I proposed a different layout where the embeddings would be placed in a dedicated table. In this post I show what performance impact it might have in comparison with storing embeddings in the same table.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I tested it
&lt;/h3&gt;

&lt;p&gt;To test queries I needed sample data with real embeddings. The reasons were explained in one of my &lt;a href="https://dev.to/googleai/how-your-sample-data-impact-vector-tests-in-postgresql-and-alloydb-211l"&gt;previous posts&lt;/a&gt; — it could have a visible impact on results when an ANN index is used. I prepared a dataset with 30k rows of sample products, built embeddings on product descriptions, and used them to fill a dedicated embedding table. For all my tests I was using an &lt;a href="https://cloud.google.com/alloydb/omni?utm_campaign=CDR_0x370c34a8_default_b561997538&amp;amp;utm_medium=external&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;AlloyDB Omni&lt;/a&gt; database. It was coming with AI integration out of the box and that helped me to &lt;a href="https://docs.cloud.google.com/alloydb/omni/containers/current/docs/ai/work-with-embeddings?utm_campaign=CDR_0x370c34a8_default_b561997538&amp;amp;utm_medium=external&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;build the embeddings&lt;/a&gt;, vector indexes, and generate search embeddings by converting search phrases to the vector variables.&lt;/p&gt;

&lt;p&gt;Here are the tables used in my tests:&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;-- ecomm.products table&lt;/span&gt;

&lt;span class="n"&gt;demodb&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;
                              &lt;span class="k"&gt;Table&lt;/span&gt; &lt;span class="nv"&gt;"ecomm.products"&lt;/span&gt;
         &lt;span class="k"&gt;Column&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Collation&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Nullable&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Default&lt;/span&gt;
&lt;span class="c1"&gt;------------------------+------------------------+-----------+----------+---------&lt;/span&gt;
 &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;character&lt;/span&gt; &lt;span class="nb"&gt;varying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;character&lt;/span&gt; &lt;span class="nb"&gt;varying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;brand&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;character&lt;/span&gt; &lt;span class="nb"&gt;varying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;retail_price&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;department&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;character&lt;/span&gt; &lt;span class="nb"&gt;varying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;sku&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;character&lt;/span&gt; &lt;span class="nb"&gt;varying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;distribution_center_id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;product_description&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;product_image_uri&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="n"&gt;Indexes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nv"&gt;"products_pkey"&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;"fk_products_distribution_center_23"&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;distribution_center_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;"idx_products_brand"&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;"idx_products_category"&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;"idx_products_retail_price"&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retail_price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;"idx_products_sku"&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;"idx_products_vector"&lt;/span&gt; &lt;span class="n"&gt;hnsw&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;vector_cosine_ops&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;Foreign&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="k"&gt;constraints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nv"&gt;"fk_products_distribution_center"&lt;/span&gt; &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;distribution_center_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;distribution_centers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Referenced&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"ecomm.inventory_items"&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="nv"&gt;"fk_inventory_items_product"&lt;/span&gt; &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"ecomm.order_items"&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="nv"&gt;"fk_order_items_product"&lt;/span&gt; &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"ecomm.product_reviews"&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="nv"&gt;"fk_reviews_product"&lt;/span&gt; &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;

&lt;span class="c1"&gt;-- ecomm.product_embeddings table&lt;/span&gt;
&lt;span class="n"&gt;demodb&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_embeddings&lt;/span&gt;
             &lt;span class="k"&gt;Table&lt;/span&gt; &lt;span class="nv"&gt;"ecomm.product_embeddings"&lt;/span&gt;
   &lt;span class="k"&gt;Column&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Collation&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Nullable&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Default&lt;/span&gt;
&lt;span class="c1"&gt;------------+-------------+-----------+----------+---------&lt;/span&gt;
 &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="n"&gt;Indexes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nv"&gt;"product_embeddings_pkey"&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;"idx_product_embeddings_vector"&lt;/span&gt; &lt;span class="n"&gt;hnsw&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;vector_cosine_ops&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;-- ecomm.inventory_items table &lt;/span&gt;
&lt;span class="n"&gt;demodb&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inventory_items&lt;/span&gt;
                                 &lt;span class="k"&gt;Table&lt;/span&gt; &lt;span class="nv"&gt;"ecomm.inventory_items"&lt;/span&gt;
             &lt;span class="k"&gt;Column&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Collation&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Nullable&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Default&lt;/span&gt;
&lt;span class="c1"&gt;--------------------------------+-----------------------------+-----------+----------+---------&lt;/span&gt;
 &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;without&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="k"&gt;zone&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;sold_at&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;without&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="k"&gt;zone&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;product_category&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;character&lt;/span&gt; &lt;span class="nb"&gt;varying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;product_name&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;character&lt;/span&gt; &lt;span class="nb"&gt;varying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;product_brand&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;character&lt;/span&gt; &lt;span class="nb"&gt;varying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;product_retail_price&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;product_department&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;character&lt;/span&gt; &lt;span class="nb"&gt;varying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;product_sku&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;character&lt;/span&gt; &lt;span class="nb"&gt;varying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;product_distribution_center_id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="n"&gt;Indexes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nv"&gt;"inventory_items_pkey"&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;"fk_inventory_items_distribution_center_8"&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_distribution_center_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;"fk_inventory_items_product_7"&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;Foreign&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="k"&gt;constraints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nv"&gt;"fk_inventory_items_distribution_center"&lt;/span&gt; &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_distribution_center_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;distribution_centers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;"fk_inventory_items_product"&lt;/span&gt; &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Referenced&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"ecomm.order_items"&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="nv"&gt;"fk_order_items_inventory_item"&lt;/span&gt; &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inventory_item_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inventory_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;-- ecomm.product_reviews table&lt;/span&gt;
&lt;span class="n"&gt;demodb&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_reviews&lt;/span&gt;
                                &lt;span class="k"&gt;Table&lt;/span&gt; &lt;span class="nv"&gt;"ecomm.product_reviews"&lt;/span&gt;
   &lt;span class="k"&gt;Column&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Collation&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Nullable&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Default&lt;/span&gt;
&lt;span class="c1"&gt;-------------+--------------------------+-----------+----------+------------------------------&lt;/span&gt;
 &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;generated&lt;/span&gt; &lt;span class="n"&gt;always&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="k"&gt;identity&lt;/span&gt;
 &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;rating&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;review_text&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
 &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="k"&gt;zone&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;
&lt;span class="n"&gt;Indexes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nv"&gt;"product_reviews_pkey"&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;"idx_product_reviews_created_at"&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;"idx_product_reviews_product_id"&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;"idx_product_reviews_user_id"&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;Check&lt;/span&gt; &lt;span class="k"&gt;constraints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nv"&gt;"product_reviews_rating_check"&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rating&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;rating&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;Foreign&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="k"&gt;constraints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nv"&gt;"fk_reviews_product"&lt;/span&gt; &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;
    &lt;span class="nv"&gt;"fk_reviews_user"&lt;/span&gt; &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ecomm.products and ecomm.product_embeddings are connected by the primary key and both tables have HNSW index built on top of the embedding vectors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test products semantic search
&lt;/h3&gt;

&lt;p&gt;I started the tests from a simple case with a cosine semantic search using only the products table and then comparing it with the join of the products and product_embeddings table. For the search I converted a phrase ‘lightweight waterproof high quality jacket’ to a vector embedding and passed it as test_vec variable. That removed the unpredictable model response time and allowed me to compare the query execution times more precisely.&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;-- Set the test_vec variable in psql&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;google_ml&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;model_id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'text-embedding-005'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'lightweight waterproof high quality jacket'&lt;/span&gt;
&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;))::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;test_vec&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;gset&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the same psql session I ran the search with EXPLAIN ANALYZE to see the execution plan and timings. Every test was repeated multiple times.&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;-- Embeddings in ecomm.products&lt;/span&gt;
&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BUFFERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retail_price&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'test_vec'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the execution plan for “inline” embeddings vector search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Limit (cost=1213.90..1240.56 rows=10 width=86) (actual time=1.112..1.200 rows=10.00 loops=1)
   Buffers: shared hit=792
   -&amp;gt; Index Scan using idx_products_vector on products p (cost=1213.90..78826.40 rows=29120 width=86) (actual time=1.110..1.197 rows=10.00 loops=1)
         Order By: (embedding &amp;lt;=&amp;gt; '[-0.01255631,...,-0.008388235]'::vector(768))
         Index Searches: 1
         Buffers: shared hit=792
 Planning:
   Buffers: shared hit=1
 Planning Time: 0.107 ms
 Execution Time: 1.224 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the plan it was visible the query was using the HNSW index to search through the embeddings and order them by similarity. The response time in total to return the results was around 2.1 ms. The response time was bigger than the pure execution time from the execution plan because it added overhead like planning, network time and client software time to show the results.&lt;/p&gt;

&lt;p&gt;Then I put embeddings to the ecomm.product_embeddings table and did the same search:&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;-- Embeddings in ecomm.product_embeddings&lt;/span&gt;
&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BUFFERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retail_price&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_embeddings&lt;/span&gt; &lt;span class="n"&gt;pe&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'test_vec'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the execution plan with join of products and product_embeddings tables using primary keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Limit (cost=219.82..250.39 rows=10 width=86) (actual time=1.188..1.351 rows=10.00 loops=1)
   Buffers: shared hit=852
   -&amp;gt; Nested Loop (cost=219.82..89223.27 rows=29120 width=86) (actual time=1.187..1.348 rows=10.00 loops=1)
         Buffers: shared hit=852
         -&amp;gt; Index Scan using idx_product_embeddings_vector on product_embeddings pe (cost=219.53..59613.60 rows=29120 width=26) (actual time=1.126..1.141 rows=10.00 loops=1)
               Order By: (embedding &amp;lt;=&amp;gt; '[-0.01255631,...,-0.008388235]'::vector(768))
               Index Searches: 1
               Buffers: shared hit=752
         -&amp;gt; Index Scan using products_pkey on products p (cost=0.29..1.01 rows=1 width=78) (actual time=0.007..0.007 rows=1.00 loops=10)
               Index Cond: (id = pe.product_id)
               Index Searches: 10
               Buffers: shared hit=50
 Planning:
   Buffers: shared hit=21
 Planning Time: 0.274 ms
 Execution Time: 1.381 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was getting top 10 vectors using the HNSW index on ecomm.product_embeddings and joining that with the ecomm.products table using the primary key. The execution time was consistently around 2.2 ms. Yes, the execution plan is a bit more complicated but the execution itself is taking only 0.1 ms longer. When I increased limit from the top 10 to top 1000 it increased the response time only by around 0.3 ms. PostgreSQL joins with primary keys are fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  Filtered semantic search
&lt;/h3&gt;

&lt;p&gt;In real life we rarely see queries using only one search condition. In most cases it involves additional parameters. I tested it adding filter on category and retail_price columns:&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;-- With two filters and embeddings in ecomm.products&lt;/span&gt;
&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BUFFERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retail_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Outerwear &amp;amp; Coats'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retail_price&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'test_vec'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The execution plan had changed adding the filter on results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Limit (cost=1213.90..1760.50 rows=10 width=97) (actual time=6.353..6.522 rows=10.00 loops=1)
   Buffers: shared hit=817
   -&amp;gt; Index Scan using idx_products_vector on products p (cost=1213.90..78829.95 rows=1420 width=97) (actual time=6.342..6.509 rows=10.00 loops=1)
         Order By: (embedding &amp;lt;=&amp;gt; '[-0.01255631,...,-0.008388235]'::vector(768))
         Filter: ((retail_price &amp;gt;= '30'::numeric) AND (retail_price &amp;lt;= '150'::numeric) AND ((category)::text = 'Outerwear &amp;amp; Coats'::text))
         Rows Removed by Filter: 7
         Index Searches: 1
         Buffers: shared hit=799
 Planning:
   Buffers: shared hit=1
 Planning Time: 0.157 ms
 Execution Time: 1.308 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response time was consistently around 2.2 ms — almost the same as without the filter.&lt;/p&gt;

&lt;p&gt;Then I executed it with embeddings in a separate table:&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;EXPLAIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BUFFERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retail_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_embeddings&lt;/span&gt; &lt;span class="n"&gt;pe&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Outerwear &amp;amp; Coats'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retail_price&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'test_vec'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I got one extra step with the filter in the execution plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Limit (cost=219.82..1346.89 rows=10 width=97) (actual time=1.167..1.370 rows=10.00 loops=1)
   Buffers: shared hit=894
   -&amp;gt; Nested Loop (cost=219.82..89370.85 rows=791 width=97) (actual time=1.166..1.367 rows=10.00 loops=1)
         Buffers: shared hit=894
         -&amp;gt; Index Scan using idx_product_embeddings_vector on product_embeddings pe (cost=219.53..59613.60 rows=29120 width=26) (actual time=1.110..1.153 rows=17.00 loops=1)
               Order By: (embedding &amp;lt;=&amp;gt; '[-0.01255631,...,-0.008388235]'::vector(768))
               Index Searches: 1
               Buffers: shared hit=759
         -&amp;gt; Index Scan using products_pkey on products p (cost=0.29..1.02 rows=1 width=89) (actual time=0.006..0.006 rows=0.59 loops=17)
               Index Cond: (id = pe.product_id)
               Filter: ((retail_price &amp;gt;= '30'::numeric) AND (retail_price &amp;lt;= '150'::numeric) AND ((category)::text = 'Outerwear &amp;amp; Coats'::text))
               Rows Removed by Filter: 0
               Index Searches: 17
               Buffers: shared hit=85
 Planning:
   Buffers: shared hit=21
 Planning Time: 0.347 ms
 Execution Time: 1.406 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response time for the query was all the time between 2.3 and 2.4 ms. Again it was slower than the query with embeddings in the same table but the difference was not significant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-tables join
&lt;/h3&gt;

&lt;p&gt;If you have a data schema where different parts and attributes of your business information are stored in different tables you use joins of multiple tables to get what you need. For tests I used a join with ecomm.product_reviews with aggregations for product ratings.&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;EXPLAIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BUFFERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retail_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&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;review_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rating&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="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_rating&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; 
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retail_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'test_vec'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;768&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;dist&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Outerwear &amp;amp; Coats'&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retail_price&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'test_vec'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_reviews&lt;/span&gt; &lt;span class="n"&gt;pr&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retail_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dist&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;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query was consistently returning results in 2.6–2.7 ms. Then I modified the query to use embeddings stored in the ecomm.product_embeddings table:&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;EXPLAIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BUFFERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retail_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&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;review_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rating&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="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_rating&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; 
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retail_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'test_vec'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;768&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;dist&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_embeddings&lt;/span&gt; &lt;span class="n"&gt;pe&lt;/span&gt;
    &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Outerwear &amp;amp; Coats'&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retail_price&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'test_vec'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;ecomm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_reviews&lt;/span&gt; &lt;span class="n"&gt;pr&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retail_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dist&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;top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference was again about 0.2 ms. The second query was returning results in 2.8–2.9 ms. I am skipping the individual execution plans for this case — they are longer and more difficult to read in a short blog post like this one, but here is a comparison diagram:&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%2Fuf6cxov2xbjl7kn3vcic.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%2Fuf6cxov2xbjl7kn3vcic.png" width="800" height="944"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the second query I had an extra join for our product_embeddings and products tables (which was expected), but apart from that it showed the similar execution path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;Here is summary table with the test results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------+-----------------------+-----------------------+---------------------+
| Test Scenario | Inline Embeddings | Dedicated Table | Overhead / Delta |
+--------------------------+-----------------------+-----------------------+---------------------+
| 1. Pure Semantic Search | Execution: ~1.22 ms | Execution: ~1.38 ms | +0.16 ms (Exec) |
| (LIMIT 10) | Total: ~2.10 ms | Total: ~2.20 ms | +0.10 ms (Total) |
+--------------------------+-----------------------+-----------------------+---------------------+
| 2. Filtered Search | Execution: ~1.31 ms | Execution: ~1.41 ms | +0.10 ms (Exec) |
| (Category + Price) | Total: ~2.20 ms | Total: 2.3 - 2.4 ms | +0.10 - 0.20 ms |
+--------------------------+-----------------------+-----------------------+---------------------+
| 3. Multi-Table Join | | | |
| (Reviews Aggregation) | Total: 2.6 - 2.7 ms | Total: 2.8 - 2.9 ms | +0.20 ms (Total) |
+--------------------------+-----------------------+-----------------------+---------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comparing queries performance for embeddings stored in the table along with the source data and in the decoupled dedicated table showed relatively minor difference in performance. In most of the cases response time didn’t exceed 0.2 ms. Of course it might be different depending on table structures, data and individual queries.&lt;/p&gt;

&lt;p&gt;I would recommend testing and considering the layout with dedicated embedding tables. It might help you in the future models maintenance and model evaluations when you just add another table with your future model and run your queries using the newly generated embeddings. With a good schema design the overhead potentially can be minimal. In my experience other factors like the model response time introduces much more variations to the response time than a join with an embedding table based on primary keys.&lt;/p&gt;




</description>
      <category>performance</category>
      <category>alloydb</category>
      <category>vectordatabase</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Equip your agent with Google Cloud best practices using google-cloud-developer plugin</title>
      <dc:creator>Remigiusz Samborski</dc:creator>
      <pubDate>Thu, 24 Sep 2026 11:55:12 +0000</pubDate>
      <link>https://dev.to/googleai/equip-your-agent-with-google-cloud-best-practices-using-google-cloud-developer-plugin-1bm1</link>
      <guid>https://dev.to/googleai/equip-your-agent-with-google-cloud-best-practices-using-google-cloud-developer-plugin-1bm1</guid>
      <description>&lt;p&gt;Coding agents in your terminal can scaffold microservices in seconds. But what happens when your agent needs to deploy directly to Google Cloud?&lt;/p&gt;

&lt;p&gt;Without cloud-specific guidance, typical agents struggle. They hallucinate obsolete SDK calls, attempt to run commands without required permissions, or silently enable billable APIs that create surprise costs on your invoice.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/google/skills/tree/main/plugins/cloud/google-cloud-developer" rel="noopener noreferrer"&gt;&lt;code&gt;google-cloud-developer&lt;/code&gt; plugin&lt;/a&gt; changes this dynamic. It equips your agent (such as &lt;a href="https://antigravity.google/product/antigravity-cli?utm_campaign=CDR_0x87fa8d40_default_b563025958&amp;amp;utm_medium=external&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;Antigravity CLI&lt;/a&gt;, &lt;a href="https://claude.com/product/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;, &lt;a href="https://learn.chatgpt.com/docs/codex/cli" rel="noopener noreferrer"&gt;Codex CLI&lt;/a&gt;) with curated skills and the &lt;a href="https://developers.google.com/knowledge/mcp?utm_campaign=CDR_0x87fa8d40_default_b563025958&amp;amp;utm_medium=external&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;Developer Knowledge MCP server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To evaluate the plugin in action, I tasked Antigravity CLI (&lt;code&gt;agy&lt;/code&gt;) with building a complete, cloud-based solution from a fresh Google Cloud project. The scenario: scaffold, containerize, and deploy a secure &lt;a href="https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/start?utm_campaign=CDR_0x87fa8d40_default_b563025958&amp;amp;utm_medium=external&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;Gemini AP&lt;/a&gt;I streaming proxy on &lt;a href="https://docs.cloud.google.com/run/docs/overview/what-is-cloud-run?utm_campaign=CDR_0x87fa8d40_default_b563025958&amp;amp;utm_medium=external&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;Cloud Run&lt;/a&gt;, complete with &lt;a href="https://docs.cloud.google.com/iam/docs?utm_campaign=CDR_0x87fa8d40_default_b563025958&amp;amp;utm_medium=external&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;Cloud IAM&lt;/a&gt; authentication and &lt;a href="https://cloud.google.com/products/firestore?utm_campaign=CDR_0x87fa8d40_default_b563025958&amp;amp;utm_medium=external&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;Firestore&lt;/a&gt; token tracking.&lt;/p&gt;

&lt;p&gt;While the resulting &lt;a href="https://github.com/rsamborski/vibecoding/tree/main/cloud-run-proxy" rel="noopener noreferrer"&gt;Cloud Run proxy codebase&lt;/a&gt; works, the proxy itself is just an example. The real value is how the plugin guides the agent through every phase of the cloud development process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Application design and SDK code generation:&lt;/strong&gt; Writing Gemini SDK code without hallucinations and selecting the right database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project onboarding, billing verification, and API enablement:&lt;/strong&gt; Auditing the project, linking a Cloud Billing account, and requesting explicit user approval before enabling billable APIs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Least-privilege IAM and Cloud Run deployment:&lt;/strong&gt; Provisioning dedicated service accounts and deploying the container asynchronously.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End-to-end verification:&lt;/strong&gt; Testing streaming and token persistence against the live service.&lt;/li&gt;
&lt;/ol&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%2Fumou2bea1rbio78nz5ms.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%2Fumou2bea1rbio78nz5ms.png" alt="Architecture and workflow overview" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick install: Five skills and an MCP server
&lt;/h2&gt;

&lt;p&gt;Remember the scene in &lt;em&gt;The Matrix&lt;/em&gt; where Trinity calls Tank for a helicopter pilot program and learns it in seconds? Adding the plugin to &lt;code&gt;agy&lt;/code&gt; works the same way with one terminal command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agy plugin &lt;span class="nb"&gt;install &lt;/span&gt;https://github.com/google/skills/plugins/cloud/google-cloud-developer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI clones the plugin repository, registers its components, and makes them available immediately.&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%2F88s8wxwrwzx6lvew9284.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%2F88s8wxwrwzx6lvew9284.png" alt="Installing the google-cloud-developer plugin" width="800" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In seconds, &lt;code&gt;agy&lt;/code&gt; configures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Five specialized skills covering Google Cloud CLI patterns and workflows.&lt;/li&gt;
&lt;li&gt;Developer Knowledge MCP server providing access to current API syntax and Google Cloud documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the plugin active, &lt;code&gt;agy&lt;/code&gt; pairs its Gemini 3.8 Flash model with authoritative cloud instructions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; As a heavy user of Google Cloud, I already have &lt;code&gt;gcloud&lt;/code&gt; installed and authenticated. I also enabled the Developer Knowledge API in my project. For new users and ones who use a different harness such as Claude Code CLI or Codex CLI, I recommend following the &lt;a href="https://docs.cloud.google.com/docs/get-started/developer-environment?utm_campaign=CDR_0x87fa8d40_default_b563025958&amp;amp;utm_medium=external&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; to get started.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Knowledge grounding: Writing modern SDK code without hallucinations
&lt;/h2&gt;

&lt;p&gt;One frequent failure mode with AI coding assistants is API obsolescence. For example, older tutorials rely on deprecated libraries such as &lt;code&gt;google-generativeai&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Because the &lt;code&gt;google-cloud-developer&lt;/code&gt; plugin connects directly to the Developer Knowledge API, the agent checks the latest official syntax before writing a single line of code.&lt;/p&gt;

&lt;p&gt;When scaffolding &lt;code&gt;app/gemini_client.py&lt;/code&gt;, &lt;code&gt;agy&lt;/code&gt; immediately used the current unified Google Gen AI SDK:&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%2Feq51kn6xfmb5m1fh4q4b.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%2Feq51kn6xfmb5m1fh4q4b.png" alt="Scaffolding code with official SDK syntax" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent produced clean Python code with Pydantic configuration, structured Cloud Logging formatters, and unit tests using &lt;code&gt;pytest&lt;/code&gt; and FastAPI's &lt;code&gt;TestClient&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Informed architecture: Evaluating database options with Developer Knowledge
&lt;/h2&gt;

&lt;p&gt;Selecting the right database for a serverless proxy requires balancing latency, connection handling, concurrency, and cost. When I asked &lt;code&gt;agy&lt;/code&gt; to track token usage per user and suggest the best database, the agent did not just pick one at random.&lt;/p&gt;

&lt;p&gt;Backed by the Developer Knowledge API, the agent pulled real-time architectural guidance and evaluated Google Cloud database options:&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%2Foab0x7klr4a6qamn7gkv.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%2Foab0x7klr4a6qamn7gkv.png" alt="Evaluating database options for the proxy service" width="799" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By grounding its analysis in the Developer Knowledge, the agent presented a clear comparison table directly in the terminal. It recommended Cloud Firestore as the most cost-effective and operationally simple fit for Cloud Run. You get an informed architectural decision based on official cloud patterns rather than guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost guardrails and onboarding: Verifying billing before enabling APIs
&lt;/h2&gt;

&lt;p&gt;Once the code and Firestore design were ready, the agent needed to enable the Gemini Enterprise, Cloud Run, and Firestore APIs in my project. Autonomous agents need strict boundaries here, because enabling an API or provisioning a managed resource can incur costs.&lt;/p&gt;

&lt;p&gt;Two skills in the &lt;code&gt;google-cloud-developer&lt;/code&gt; plugin coordinate this step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pre-flight billing audit:&lt;/strong&gt; Before enabling billable services, the &lt;code&gt;google-cloud-recipe-onboarding&lt;/code&gt; skill instructs the agent to verify that the target project is linked to an active Cloud Billing account (&lt;code&gt;gcloud billing projects describe&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit user consent:&lt;/strong&gt; The &lt;code&gt;gcloud&lt;/code&gt; skill requires explicit user approval before running &lt;code&gt;gcloud services enable&lt;/code&gt; or any destructive action.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When &lt;code&gt;agy&lt;/code&gt; identified the required APIs for the proxy, it first checked my project state and paused for explicit permission:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The system's guardrails require explicit user approval before enabling any API, specifically addressing potential security risks and unexpected costs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This keeps you in the driver's seat and eliminates accidental spend.&lt;/p&gt;

&lt;h3&gt;
  
  
  How &lt;code&gt;agy&lt;/code&gt; helps: Scheduling timers for billing propagation
&lt;/h3&gt;

&lt;p&gt;During the onboarding skill's pre-flight check, I linked a new billing account to my test project so &lt;code&gt;agy&lt;/code&gt; could enable the APIs. In Google Cloud, linking a billing account often takes a few minutes to propagate across backend systems.&lt;/p&gt;

&lt;p&gt;While the &lt;code&gt;google-cloud-developer&lt;/code&gt; plugin tells the agent &lt;em&gt;how&lt;/em&gt; to validate billing status (&lt;code&gt;gcloud billing projects describe&lt;/code&gt; checking for &lt;code&gt;billingEnabled: true&lt;/code&gt;), &lt;code&gt;agy&lt;/code&gt; complements the plugin with its native &lt;code&gt;Schedule&lt;/code&gt; tool so the session does not fail or spin in a retry loop.&lt;/p&gt;

&lt;p&gt;I typed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Ok. Let's wait now. I just enabled the billing and it needs to propagate. I'll check in 10 minutes."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent scheduled a 600-second timer and freed the terminal prompt:&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%2Fyasaane47mgblblj05ur.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%2Fyasaane47mgblblj05ur.png" alt="Scheduling a billing timer" width="800" height="127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exactly 10 minutes later, the timer woke the agent up. It resumed the exact conversation context, checked the billing status with &lt;code&gt;gcloud beta billing projects describe&lt;/code&gt;, verified that &lt;code&gt;billingEnabled&lt;/code&gt; was &lt;code&gt;true&lt;/code&gt;, and prompted to enable the required APIs:&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%2F23rhikrpb8jj7yixczb8.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%2F23rhikrpb8jj7yixczb8.png" alt="Timer triggered and billing confirmed" width="799" height="126"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can walk away, grab a coffee, and let your agent resume right where you left off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security enforcement: Least-privilege IAM
&lt;/h2&gt;

&lt;p&gt;With the APIs enabled, the next step before deployment was configuring authentication. Without guidance, agents often suggest creating a long-lived Gemini API key or downloading a service account JSON key file.&lt;/p&gt;

&lt;p&gt;Instead, the &lt;code&gt;google-cloud-recipe-auth&lt;/code&gt; skill explicitly forbids downloaded service account keys and steers the agent toward Google Cloud security best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dedicated service identity:&lt;/strong&gt; Creates a workload-specific service account (&lt;code&gt;gemini-proxy-sa&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Least-privilege IAM bindings:&lt;/strong&gt; Grants only &lt;code&gt;roles/aiplatform.user&lt;/code&gt; for Gemini invocation and &lt;code&gt;roles/datastore.user&lt;/code&gt; for Firestore token logging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authenticated ingress:&lt;/strong&gt; Protects the Cloud Run endpoint with &lt;code&gt;--no-allow-unauthenticated&lt;/code&gt; so the Google Frontend validates &lt;a href="https://docs.cloud.google.com/docs/authentication/get-id-token?utm_campaign=CDR_0x87fa8d40_default_b563025958&amp;amp;utm_medium=external&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;Google-signed OpenID Connect (OIDC) ID tokens&lt;/a&gt; before traffic reaches your container.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No secrets live in plaintext, and permissions stay tightly scoped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud Run deployment
&lt;/h2&gt;

&lt;p&gt;To deploy the container, &lt;code&gt;agy&lt;/code&gt; used &lt;code&gt;finding-google-skills&lt;/code&gt; to pull Cloud Run deployment patterns (&lt;code&gt;cloud-run-basics&lt;/code&gt;) from the remote skill catalog and combined them with the &lt;code&gt;gcloud&lt;/code&gt; skill's command formatting rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud run deploy gemini-38-flash-proxy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gemini-proxy-sa@[PROJECT_ID].iam.gserviceaccount.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How &lt;code&gt;agy&lt;/code&gt; helps: Running long tasks in the background
&lt;/h3&gt;

&lt;p&gt;Building a container image from source still takes a few minutes. &lt;code&gt;agy&lt;/code&gt; helps by launching the deployment as a background task instead of locking up your interactive session:&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%2Fiikanzccwusv0gw5d7qk.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%2Fiikanzccwusv0gw5d7qk.png" alt="Cloud Run deployment running in the background" width="800" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the task status bar in the terminal: &lt;code&gt;[14:12:41] gcloud run deploy gemini-38-flash-proxy ... running (1 task(s) · /tasks)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can continue chatting with the agent, ask questions, or inspect background jobs with &lt;code&gt;/tasks&lt;/code&gt; while Cloud Build compiles your container image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live verification: Streaming and per-user token persistence
&lt;/h2&gt;

&lt;p&gt;Once the background deployment completed, &lt;code&gt;agy&lt;/code&gt; generated test commands to verify the end-to-end flow.&lt;/p&gt;

&lt;p&gt;Calling the usage endpoint with an authenticated identity token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; GET https://gemini-38-flash-proxy-[PROJECT_NUMBER].us-central1.run.app/v1/users/me/usage &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gcloud auth print-identity-token&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns real-time Firestore persistence data:&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;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[EMAIL]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_output_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-21T12:21:21.567000+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"models.gemini-3_8-flash.input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"models.gemini-3_8-flash.output_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"models.gemini-3_8-flash.total_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;55&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;p&gt;Atomic increments in Firestore record prompt and completion token counts across requests, giving you immediate visibility into user consumption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Pairing &lt;code&gt;agy&lt;/code&gt; with the &lt;code&gt;google-cloud-developer&lt;/code&gt; plugin creates a safer, smarter CLI workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Knowledge grounding: Developer Knowledge API prevents outdated SDK hallucinations and ensures accurate code generation.&lt;/li&gt;
&lt;li&gt;Human in the loop: Built-in skill guardrails require explicit permission before running commands that can incur cloud costs.&lt;/li&gt;
&lt;li&gt;Non-blocking workflow: Scheduled timers handle asynchronous cloud operations, while background tasks let long builds run without freezing your prompt.&lt;/li&gt;
&lt;li&gt;Enterprise security defaults: Encourages least-privilege service accounts and IAM authentication over hardcoded secrets.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Check out more resources on &lt;code&gt;google-cloud-developer&lt;/code&gt; plugin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/google/skills/tree/main/plugins/cloud/google-cloud-developer" rel="noopener noreferrer"&gt;google-cloud-developer plugin on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/blog/topics/developers-practitioners/introducing-the-google-cloud-developer-plugin-for-ai-coding-agents?utm_campaign=CDR_0x87fa8d40_default_b563025958&amp;amp;utm_medium=external&amp;amp;utm_source=blog" rel="noopener noreferrer"&gt;Official announcement on the Google Cloud blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://g.dev/cloud/agent-plugins-codelab-agy" rel="noopener noreferrer"&gt;Hands-on codelab: Install and use Google Cloud Developer plugin in Antigravity&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Connect with me
&lt;/h2&gt;

&lt;p&gt;I am always eager to share what I've learned and hear how fellow developers and AI enthusiasts use Antigravity and Google Cloud. If you found this article helpful, feel free to share it and follow me on your favorite social platform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/remigiusz-samborski/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://x.com/RemikSamborski" rel="noopener noreferrer"&gt;X&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bsky.app/profile/rsamborski.bsky.social" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>agentskills</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>Claude Opus 5.5 is now available on Google Cloud</title>
      <dc:creator>Jen Harvey</dc:creator>
      <pubDate>Tue, 22 Sep 2026 17:48:24 +0000</pubDate>
      <link>https://dev.to/googleai/claude-opus-55-is-now-available-on-google-cloud-2oh</link>
      <guid>https://dev.to/googleai/claude-opus-55-is-now-available-on-google-cloud-2oh</guid>
      <description>&lt;p&gt;Everyday complex tasks? No problem. Opus 5.5 handles long-running coding and knowledge work while keeping you in the loop along the way, all at a lower price per token. &lt;a href="https://console.cloud.google.com/agent-platform/publishers/anthropic/model-garden/claude-opus-5-5" rel="noopener noreferrer"&gt;Get started in Model Garden today&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>claude</category>
      <category>googlecloud</category>
    </item>
  </channel>
</rss>
