<?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: Denis Valášek</title>
    <description>The latest articles on DEV Community by Denis Valášek (@denisvalasek).</description>
    <link>https://dev.to/denisvalasek</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F407515%2Ff55e315f-1697-4014-9102-b9ba334af7f4.jpg</url>
      <title>DEV Community: Denis Valášek</title>
      <link>https://dev.to/denisvalasek</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/denisvalasek"/>
    <language>en</language>
    <item>
      <title>The Power of Gemini inside Trello: Building an LLM Assistant with Firebase Genkit</title>
      <dc:creator>Denis Valášek</dc:creator>
      <pubDate>Sun, 21 Dec 2025 16:51:06 +0000</pubDate>
      <link>https://dev.to/denisvalasek/the-power-of-gemini-inside-trello-building-an-llm-assistant-with-firebase-genkit-3dh2</link>
      <guid>https://dev.to/denisvalasek/the-power-of-gemini-inside-trello-building-an-llm-assistant-with-firebase-genkit-3dh2</guid>
      <description>&lt;p&gt;In the world of project management, Trello has long been a favorite for its simplicity and visual organization. But what if your Trello cards could talk back? What if a bot could analyze your card's details, look at your attachments, and provide intelligent updates or answers to your team's comments?&lt;/p&gt;

&lt;p&gt;In this article, we'll explore how to integrate Google's Gemini 3.0 Flash model into Trello using Firebase Genkit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we dive in, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://trello.com/" rel="noopener noreferrer"&gt;Trello accounts&lt;/a&gt; (One bot account, one to issue requests from)&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://aistudio.google.com/" rel="noopener noreferrer"&gt;Google AI API Key&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; installed&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/" rel="noopener noreferrer"&gt;Firebase/GCP account&lt;/a&gt; (for telemetry and deployment)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting up your Trello Power-Up
&lt;/h2&gt;

&lt;p&gt;To interact with Trello's API, you'll need to create a Power-Up. This gives you the API Key and Token required for authentication.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Go to the &lt;a href="https://trello.com/power-ups/admin/new" rel="noopener noreferrer"&gt;Trello Power-Up Admin&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; Create a new Power-Up for your workspace.
&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.amazonaws.com%2Fuploads%2Farticles%2Fwzh1gfad7ypqmxt5vwfg.png" alt=" "&gt;
&lt;/li&gt;
&lt;li&gt; Generate your &lt;strong&gt;API Key&lt;/strong&gt; and &lt;strong&gt;Secret&lt;/strong&gt;.&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.amazonaws.com%2Fuploads%2Farticles%2Fobdz9c9ezndr6cbxzgu7.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.amazonaws.com%2Fuploads%2Farticles%2Fobdz9c9ezndr6cbxzgu7.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Generate a &lt;strong&gt;Token&lt;/strong&gt; to allow your app to act on behalf of your user.&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.amazonaws.com%2Fuploads%2Farticles%2F2cbt6boc5fe3qru6lom7.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.amazonaws.com%2Fuploads%2Farticles%2F2cbt6boc5fe3qru6lom7.png" alt=" "&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%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftq4qrlwtatvblgeea609.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.amazonaws.com%2Fuploads%2Farticles%2Ftq4qrlwtatvblgeea609.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep these credentials safe! You'll need them for your &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crafting the AI Logic with Firebase Genkit
&lt;/h2&gt;

&lt;p&gt;Firebase Genkit simplifies building LLM-powered applications. We'll define a &lt;strong&gt;Flow&lt;/strong&gt; that takes a Trello card ID and a comment, fetches all relevant data, and uses Gemini to generate a response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining the Schema
&lt;/h3&gt;

&lt;p&gt;First, we define a structured schema for our card data. This ensures Gemini receives consistent information, including checklists, comments, and even image attachments. It's possible to easily extend this schema for other details, like links to other projects, more cards etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TrelloRequestRequestInputSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User's instructions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;})),&lt;/span&gt;
  &lt;span class="na"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;})),&lt;/span&gt;
  &lt;span class="na"&gt;checklists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;itemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;dueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;})),&lt;/span&gt;
  &lt;span class="na"&gt;today&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Trello Flow
&lt;/h3&gt;

&lt;p&gt;The core of our application is the &lt;code&gt;trelloFlow&lt;/code&gt;. It fetches the card details using a helper function and then calls our prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trelloFlow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;trelloFlow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;trelloCardId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;trelloComment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formattedCard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getFormattedTrelloCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trelloCardId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trelloComment&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;trelloPrompt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;inputData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;formattedCard&lt;/span&gt;   &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;trelloClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addCardComment&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trelloCardId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Comment added successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Multi-modal Capabilities: Seeing Attachments
&lt;/h2&gt;

&lt;p&gt;One of Gemini's greatest strengths is its ability to understand images. Our bot can "see" attachments on a Trello card. In &lt;code&gt;trello_helper.ts&lt;/code&gt;, we fetch attachments and convert them to base64 data URLs, which Genkit can pass directly to the LLM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formattedAttachments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attachment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attachment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`OAuth oauth_consumer_key="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TRELLO_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;", oauth_token="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TRELLO_API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`data:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;attachment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mimeType&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;base64,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;attachment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Designing the Prompt with Dot-Prompt
&lt;/h3&gt;

&lt;p&gt;Genkit introduces the &lt;code&gt;.prompt&lt;/code&gt; file format, which is incredibly powerful for keeping your AI logic clean and separate from your application code. In &lt;code&gt;prompts/trello.prompt&lt;/code&gt;, the Gemini 3.0 Flash model was selected for its speed and native multi-modal support. &lt;/p&gt;

&lt;p&gt;The prompt uses Handlebars-style templates to inject our structured card data. We loop over the different parts of the Trello card, but this can be used to inject other context as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting Trello via Webhooks
&lt;/h2&gt;

&lt;p&gt;To make the bot reactive, we use Trello Webhooks. Whenever a comment is added, Trello sends a POST request to our application.&lt;/p&gt;

&lt;p&gt;Different Express middleware is used to intercept, verify and format the requests, before they reach Genkit. &lt;/p&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;p&gt;We implement a middleware to verify that incoming requests actually come from Trello using HMAC-SHA1 signatures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trelloSignatureMiddleware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestWithAuth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextFunction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-trello-webhook&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TRELLO_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callbackURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TRELLO_CALLBACK_URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;callbackURL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;computedSignature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;computedSignature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Registering the Webhook
&lt;/h2&gt;

&lt;p&gt;Once your app is deployed (e.g., to Cloud Run), you need to tell Trello where to send its updates. Make sure to fill out your deployment URL into the .env file. I've included a handy script for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx tsx src/register_webhook.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;69457XXXXXXXXXXXXXXX
Registering webhook for https://genkit-trello-{PROJECT_ID}.europe-west1.run.app/trelloFlow with board ID: 6945713XXXXXXXXXXXXXX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Seeing it in Action
&lt;/h2&gt;

&lt;p&gt;Once everything is set up, you can tag your bot in a Trello comment (e.g., &lt;code&gt;@GeminiBot what's the status of this task?&lt;/code&gt;). The bot will analyze the entire card context and reply.&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.amazonaws.com%2Fuploads%2Farticles%2Fwd4gj2ure5o368r604tw.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.amazonaws.com%2Fuploads%2Farticles%2Fwd4gj2ure5o368r604tw.png" alt="Conv example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It can even track progress based on checklists and card descriptions!&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.amazonaws.com%2Fuploads%2Farticles%2F7zrg16yp3iemhigncvcm.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.amazonaws.com%2Fuploads%2Farticles%2F7zrg16yp3iemhigncvcm.png" alt="Conv progress"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring
&lt;/h2&gt;

&lt;p&gt;If we deploy to GCP and have the Firebase project setup, in the Genkit part of the dashboard, we get important debug and overview information right away. &lt;br&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.amazonaws.com%2Fuploads%2Farticles%2F66v7dx7mg0uc8mjlqs7o.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.amazonaws.com%2Fuploads%2Farticles%2F66v7dx7mg0uc8mjlqs7o.png" alt="Firebase Genkit dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By combining Trello's structured project data with Gemini's reasoning capabilities and Firebase Genkit's orchestration, you can create a helpful Trello assistant. Whether it's summarizing long comment threads, analyzing attached designs, or providing quick status updates, there are many ways for this to be useful. The project is also just a MVP. Feel free to fork it and extend it to your use-cases. &lt;/p&gt;

&lt;p&gt;Check out the full source code at &lt;a href="https://github.com/DenisVCode/genkit-trello-bot" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>gemini</category>
      <category>genkit</category>
      <category>ai</category>
    </item>
    <item>
      <title>Set up RAG with Genkit and Firebase in 15 minutes</title>
      <dc:creator>Denis Valášek</dc:creator>
      <pubDate>Mon, 03 Nov 2025 14:39:17 +0000</pubDate>
      <link>https://dev.to/denisvalasek/set-up-rag-with-genkit-and-firebase-in-15-minutes-50b2</link>
      <guid>https://dev.to/denisvalasek/set-up-rag-with-genkit-and-firebase-in-15-minutes-50b2</guid>
      <description>&lt;h3&gt;
  
  
  Intro
&lt;/h3&gt;

&lt;p&gt;Retrieval augmented generation (RAG) is like a knowledge base to your LLM model that helps it to add your context-specific information into the prompt and answer. In this article, we will go through how to set up an endpoint that will provide an up-to-date answer about Genkit documentation thanks to RAG.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Firebase project with billing enabled (you won't be charged if you follow along, but you need to have billing enabled to access Firestore and Firebase Cloud Functions)

&lt;ul&gt;
&lt;li&gt;If you have never used Firebase, check out how to get started on the &lt;a href="https://firebase.blog/posts/2024/11/claim-300-to-get-started" rel="noopener noreferrer"&gt;Firebase Blog&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://genkit.dev/docs/get-started/" rel="noopener noreferrer"&gt;Genkit installed&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Genkit is an open-source library that abstracts much of the boring stuff when building AI-powered apps and adds great observability and dev tools on top.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Node.js/local dev environment with gcloud CLI&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Getting the data &amp;amp; Chunking
&lt;/h3&gt;

&lt;p&gt;Genkit uses Flows for organization, you can check out &lt;a href="https://dev.to/denisvalasek/understanding-genkit-flows-with-czech-language-tricks-26i3"&gt;my other article&lt;/a&gt; to better understand the concept. Let's start with getting the data of the Genkit documentation. &lt;/p&gt;

&lt;p&gt;The Firebase team provides the &lt;a href="https://genkit.dev/llms.txt" rel="noopener noreferrer"&gt;Genkit documentation&lt;/a&gt; via the &lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;llms.txt standard&lt;/a&gt;, which means the whole documentation is segmented into Markdown documents. &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.amazonaws.com%2Fuploads%2Farticles%2Fm2knpd1bl28s3i4psix5.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.amazonaws.com%2Fuploads%2Farticles%2Fm2knpd1bl28s3i4psix5.png" alt="Genkit llms.txt" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the demo, we will be only interested in the JavaScript part of the documentation, so we can use that domain-specific URL for getting the data. Since the data is provided as text, we don't need any complicated APIs or processing. The LLM will understand Markdown just fine. But to provide specific context, we need to split the large Markdown document into smaller parts - chunks. &lt;br&gt;
There are &lt;a href="https://www.pinecone.io/learn/chunking-strategies/" rel="noopener noreferrer"&gt;many different chunking strategies&lt;/a&gt;, we will use a fairly simple setup, split by sentences, with minimum chunk length of 1000 characters and max. 2000. We will also have 100 character overlap, to keep context between the chunks. &lt;/p&gt;

&lt;p&gt;Here are two config objects that we will reuse through this demo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunkingConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;splitter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sentence&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;overlap&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="na"&gt;delimiters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;indexConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Firestore collection name&lt;/span&gt;
  &lt;span class="na"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;genkitDocs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Firestore field name for the content&lt;/span&gt;
  &lt;span class="na"&gt;contentField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Firestore field name for the vector&lt;/span&gt;
  &lt;span class="na"&gt;vectorField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;embedding&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Embedder to use for the vectorization&lt;/span&gt;
  &lt;span class="na"&gt;embedder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;googleAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embedder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gemini-embedding-001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Dimension of the vector, it's important to use 2048 when using Firestore as the Vector DB&lt;/span&gt;
    &lt;span class="na"&gt;outputDimensionality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Embedding
&lt;/h3&gt;

&lt;p&gt;The LLMs understand the text differently than we do, that's why we can make it easier for them to understand using embeddings. &lt;br&gt;
In reality it means, that we will convert the text into vectors that can be used by the model to find relevant pieces of text more efficiently. &lt;/p&gt;

&lt;p&gt;Once again, Genkit makes it easy for us to use the Google gemini-embedding-001 model to parse the chunked text into embeddings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;indexToFirestore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textChunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;textChunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;embedder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;indexConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;embedder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;outputDimensionality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;firestore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;indexConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;indexConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vectorField&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;FieldValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;indexConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentField&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function will take the created chunks, embed them into vectors and save the results into &lt;a href="https://firebase.google.com/docs/firestore/" rel="noopener noreferrer"&gt;Firebase Firestore database&lt;/a&gt;. It's important to also save the original text, so we can show it back to the user if necessary or reference it in other ways. It's easier to think about it if you were embedding an FAQ document, you can point the user to the specific question/answer in the document alongside the LLM generated answer by the model. &lt;/p&gt;

&lt;h3&gt;
  
  
  Indexing flow
&lt;/h3&gt;

&lt;p&gt;Here is the full flow (missing the required file imports that can be found at the end of the article alongside the full code sample) that we can use to get, chunk, embed and save the Genkit JS documentation, or any other markdown file URL for that matter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;indexGenkit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;indexGenkit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;urlLlmsTxt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;URL for the .llms docs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://genkit.dev/llms-js.txt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;documentsIndexed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;urlLlmsTxt&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Download the Genkit documentation as Markdown text&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;docTxt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;extract-text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urlLlmsTxt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="c1"&gt;// Chunk the documentation into smaller pieces with the defined chunking config&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chunk-it&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;docTxt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chunkingConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Delete all existing documents in the collection to avoid duplicates when reindexing&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;delete-existing-documents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;deleteAllDocumentsInCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;indexConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="c1"&gt;// Create Genkit documents from the chunks&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;urlLlmsTxt&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="c1"&gt;// Save the created documents to Firestore&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;index-to-firestore&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;indexToFirestore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;documentsIndexed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;documentsIndexed&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use the &lt;a href="https://docs.cloud.google.com/sdk/docs/install" rel="noopener noreferrer"&gt;gcloud CLI&lt;/a&gt; to create the required index for Firestore to use embeddings, just make sure to use your project ID and change any fields values if you are not using the config provided above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud firestore indexes composite create &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;={{&lt;/span&gt;FIREBASE_PROJECT_SLUG_ID&lt;span class="o"&gt;}}&lt;/span&gt; &lt;span class="nt"&gt;--collection-group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;genkitDocs &lt;span class="nt"&gt;--query-scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COLLECTION &lt;span class="nt"&gt;--field-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vector-config&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{"dimension":"2048","flat": "{}"}'&lt;/span&gt;,field-path&lt;span class="o"&gt;=&lt;/span&gt;embedding
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Retrieving data
&lt;/h3&gt;

&lt;p&gt;In RAG, we have to create &lt;a href="https://genkit.dev/docs/rag/#retrievers" rel="noopener noreferrer"&gt;Retrievers&lt;/a&gt; to get the embedded or other data. Genkit provides a pre-created Firestore retriever, but you can learn how to create your own retriever in the &lt;a href="https://genkit.dev/docs/rag/#write-your-own-indexers-and-retrievers" rel="noopener noreferrer"&gt;Genkit documentation&lt;/a&gt; or finish this demo and ask your helpful RAG-powered assistant :)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firestoreRetriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineFirestoreRetriever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;firestoreRetriever&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Firestore instance&lt;/span&gt;
  &lt;span class="nx"&gt;firestore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Firestore collection name&lt;/span&gt;
  &lt;span class="na"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;indexConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Firestore field name for the content&lt;/span&gt;
  &lt;span class="na"&gt;contentField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;indexConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Firestore field name for the vector&lt;/span&gt;
  &lt;span class="na"&gt;vectorField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;indexConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vectorField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Embedder to use for the vectorization&lt;/span&gt;
  &lt;span class="na"&gt;embedder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;indexConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;embedder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Distance measure to use for the similarity search&lt;/span&gt;
  &lt;span class="na"&gt;distanceMeasure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;COSINE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The finished Flow
&lt;/h3&gt;

&lt;p&gt;Putting it all together, we have the &lt;code&gt;genkitQAFlow&lt;/code&gt;, that takes in the question, uses the Firestore Retriever to get the relevant documents and sends them to the LLM model (gemini-2.5-flash in this case) to generate the answer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;genkitQAFlow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;genkitQA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;What is Genkit?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Retrieve relevant documents&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="c1"&gt;// Retriever to use for the similarity search using Firestore as the Vector DB&lt;/span&gt;
      &lt;span class="na"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;firestoreRetriever&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Number of top documents to retrieve&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Generate a response&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;googleAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
You are acting as a helpful AI assistant that can answer
questions about the Genkit documentation.

Use only the context provided to answer the question.

Question: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Trying it out
&lt;/h3&gt;

&lt;p&gt;You can find the &lt;a href="https://gist.github.com/DenisVCode/87b2d150cd299dab248c6c64f707186a" rel="noopener noreferrer"&gt;full code sample on GitHub&lt;/a&gt;. The sample contains Firebase setup, env and other parts, that are not necessary for the article, but are required to run the code. &lt;/p&gt;

&lt;p&gt;When first running the retrieval Genkit QA flow, you will get an error, asking you to create a relevant Firestore index. Just follow the URL provided in the error to create the index. &lt;/p&gt;

&lt;p&gt;Now you can run the following command, to start the Genkit dev UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;genkit start &lt;span class="nt"&gt;--&lt;/span&gt; tsx &lt;span class="nt"&gt;--watch&lt;/span&gt; src/genkit-sample.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can visit &lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;http://localhost:4000&lt;/a&gt; to see the Genkit Dev UI in the browser. Select Flows and the indexGenkit flow. &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.amazonaws.com%2Fuploads%2Farticles%2F804p1ycrslw237bkxme9.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.amazonaws.com%2Fuploads%2Farticles%2F804p1ycrslw237bkxme9.png" alt="Genkit index docs flow" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After pressing run, it will take about 2 minutes to get, chunk, index and save the required documents. For larger datasets, it would be better to optimize this process. &lt;/p&gt;

&lt;p&gt;After successful run, you can see the spans along with the details in the right output panel. &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.amazonaws.com%2Fuploads%2Farticles%2Faafx9bfi72kgwzpx9hc4.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.amazonaws.com%2Fuploads%2Farticles%2Faafx9bfi72kgwzpx9hc4.png" alt="Genkit index docs flow result" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the indexing, we can switch to the &lt;code&gt;genkitQA&lt;/code&gt; &lt;a href="http://localhost:4000/flows/genkitQA" rel="noopener noreferrer"&gt;flow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can ask question in the input field and wait for the answer. On the right side we can once again see what is actually happening in the flow - the retriever, embedding and finally putting it all together in the call to the Gemini API.&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.amazonaws.com%2Fuploads%2Farticles%2Fkvi5k9wzt5aiubpjb4w8.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.amazonaws.com%2Fuploads%2Farticles%2Fkvi5k9wzt5aiubpjb4w8.png" alt="Genkit QA answer" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;We have explored the basics of RAG and Genkit, you should be able to take any long-form text and create custom embeddings to answer your domain-specific questions. Using Firebase, we have explored Firestore as a quick and cheap (free if you are within limits) vector database. &lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://www.linkedin.com/in/dominik-simonik/" rel="noopener noreferrer"&gt;Dominik Šimoník&lt;/a&gt; as a co-author of this article that is based on our talk at &lt;a href="https://devfest.cz/" rel="noopener noreferrer"&gt;DevFest.cz 2025&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>firebase</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Gemini in your Slack workspace using Firebase &amp; Genkit</title>
      <dc:creator>Denis Valášek</dc:creator>
      <pubDate>Fri, 27 Jun 2025 10:16:38 +0000</pubDate>
      <link>https://dev.to/denisvalasek/gemini-in-your-slack-workspace-using-firebase-genkit-530c</link>
      <guid>https://dev.to/denisvalasek/gemini-in-your-slack-workspace-using-firebase-genkit-530c</guid>
      <description>&lt;p&gt;Ever wished you could just tag your custom AI model in Slack? With connection to your internal systems (and your safeguards), to get actually relevant data along with helpful monitoring of the usage and access?&lt;/p&gt;

&lt;p&gt;Let's take a look how you can do that today using &lt;a href="https://www.npmjs.com/package/@slack/bolt" rel="noopener noreferrer"&gt;Slack Bolt API&lt;/a&gt;, &lt;a href="https://ai.google.dev/" rel="noopener noreferrer"&gt;Google Gemini&lt;/a&gt; and &lt;a href="https://genkit.dev/" rel="noopener noreferrer"&gt;Firebase Genkit&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Slack setup
&lt;/h3&gt;

&lt;p&gt;We need to create a Slack app that will handle all the Slack permissions, events, and interactions. It's fairly easy to do this for your local workspace if you are an admin, you might need additional permissions if you are not, or just create a new workspace for testing.&lt;/p&gt;

&lt;p&gt;Head on to &lt;a href="https://api.slack.com/apps" rel="noopener noreferrer"&gt;Slack Apps&lt;/a&gt; to create a new Slack app from scratch.&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.amazonaws.com%2Fuploads%2Farticles%2Fl7k25awtm8wwfya735ul.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.amazonaws.com%2Fuploads%2Farticles%2Fl7k25awtm8wwfya735ul.png" alt="Create new Slack app from scratch" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure to pick some reasonable name, as it will be used to tag the bot later on and install it into the appropriate workspace.&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.amazonaws.com%2Fuploads%2Farticles%2Fk68oew65e668idp7tyyz.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.amazonaws.com%2Fuploads%2Farticles%2Fk68oew65e668idp7tyyz.png" alt="Pick name and Workspace" width="800" height="768"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After creating, you are presented with a bunch of secrets and values. You will need &lt;strong&gt;Signing Secret&lt;/strong&gt; later on, but it's always available on this screen.&lt;/p&gt;

&lt;p&gt;Go to &lt;strong&gt;OAuth &amp;amp; Permissions&lt;/strong&gt; section in the left panel and pick 2 Bot Token Scopes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;app_mentions:read&lt;/strong&gt; to get events when the bot was mentioned&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;chat:write&lt;/strong&gt; to be able to reply to the mentions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe14d1bintwiv9lh3o7g9.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.amazonaws.com%2Fuploads%2Farticles%2Fe14d1bintwiv9lh3o7g9.png" alt="OAuth &amp;amp; Permissions" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Proceed to install the app into your workspace in &lt;strong&gt;OAuth Tokens&lt;/strong&gt;, this will give you a bot token, with the permissions you set above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend setup
&lt;/h3&gt;

&lt;p&gt;For our backend we will be using &lt;a href="https://firebase.google.com/docs/functions/get-started?gen=2nd" rel="noopener noreferrer"&gt;Cloud Functions for Firebase&lt;/a&gt; and &lt;a href="https://genkit.dev/" rel="noopener noreferrer"&gt;Firebase Genkit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Cloud Functions will provide us with serverless webhook URL, that we can use for our Slack app and Genkit will make it easy for us to iterate on prompt design, switch models and get step-by-step logs of our AI requests. &lt;/p&gt;

&lt;p&gt;Follow the README in the sample &lt;a href="https://github.com/DenisVCode/gemini-in-slack" rel="noopener noreferrer"&gt;Gemini in Slack repository&lt;/a&gt; for this article to have the project setup.&lt;/p&gt;

&lt;h4&gt;
  
  
  Genkit
&lt;/h4&gt;

&lt;p&gt;In the &lt;a href="https://github.com/DenisVCode/gemini-in-slack/blob/main/functions/src/genkit.ts" rel="noopener noreferrer"&gt;genkit.ts&lt;/a&gt; file, you can see several Genkit concepts being used, let's highlight a few:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;enableFirebaseTelemetry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single line gives us a complete overview &amp;amp; traces for each API call we make using Genkit, making it very easy to keep track of our spend, usage and how well the app works. You can find all the live calls in the Firebase dashboard in the Genkit section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slackFlow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;slackFlow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Slack message text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c1"&gt;// Edit the prompt to fit your use case, make sure to experiment with the models and tools to get the best results.&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`You are a helpful assistant that can answer questions about the number of customers and revenue for a given date. You can use the tools provided to you to get the information you need. You might also answer general questions about the workspace.

   Here is the user's message: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


   &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;slackFlow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;


   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
     &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gemini20Flash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;},&lt;/span&gt;
     &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;getCustomersForDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getRevenueForDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
   &lt;span class="p"&gt;});&lt;/span&gt;


   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code snippet represents a &lt;a href="https://genkit.dev/docs/flows/" rel="noopener noreferrer"&gt;Genkit flow&lt;/a&gt;, which is like a Genkit function wrapper, which handles many internal tasks for one goal. Inside one flow, you might do several AI calls, call internal APIs or access the file system, but it makes it incredibly easy to access the function from the Genkit Dev Tools and to categorize it in Genkit Monitoring.&lt;/p&gt;

&lt;p&gt;For simplicity, prompt is also defined right in the flow, but we could use &lt;a href="https://genkit.dev/docs/dotprompt/" rel="noopener noreferrer"&gt;Genkit Dotprompt&lt;/a&gt; if our prompt grows or we want to test multiple versions efficiently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getCustomersForDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;getCustomersForDate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Gets the number of customers for a given date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
     &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The date to get the number of customers for&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;}),&lt;/span&gt;
   &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
     &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The number of customers for the given date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
     &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The error message text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
   &lt;span class="p"&gt;}),&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;getCustomersForDate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
     &lt;span class="nf"&gt;checkIfFutureDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="c1"&gt;// Here, we would typically make an API call or database query. For this&lt;/span&gt;
     &lt;span class="c1"&gt;// example, we just return a random value.&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
     &lt;span class="p"&gt;};&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;getCustomersForDate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I cannot provide data for the future dates.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;};&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This part showcases how &lt;a href="https://genkit.dev/docs/tool-calling/#overview-of-tool-calling" rel="noopener noreferrer"&gt;tool calling&lt;/a&gt; works in Genkit. We define tools and their parameters, along with descriptions to be later on passed to the model itself. Recently, Genkit also started supporting &lt;a href="https://genkit.dev/docs/tool-calling/#dynamically-defining-tools-at-runtime" rel="noopener noreferrer"&gt;dynamic tool creation&lt;/a&gt; at runtime, which might be interesting for some use cases.&lt;/p&gt;

&lt;p&gt;In the sample code tool calling is used as an example to call your internal APIs to provide answers to various questions, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many customers did we have yesterday?&lt;/li&gt;
&lt;li&gt;What is our revenue today?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also provided some examples on how to handle errors in tool calling and some potential logical errors as well, like asking for revenue in the future.&lt;/p&gt;

&lt;h4&gt;
  
  
  Local testing
&lt;/h4&gt;

&lt;p&gt;Once you go through the codebase, set your own secrets in the .env file, you might want to play with the prompt itself and the tool calling, to better fit your use-case.&lt;/p&gt;

&lt;p&gt;You can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run genkit:start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To start the Genkit Dev UI, you will get a localhost URL as the output. In the left panel, select Flows and the slackFlow to start testing. You can treat the input field as whatever the Slack message would say and on the right side you can see step-by-step calls made inside the flows for easy debugging.&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.amazonaws.com%2Fuploads%2Farticles%2Fcduvdcwa68275usdxia6.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.amazonaws.com%2Fuploads%2Farticles%2Fcduvdcwa68275usdxia6.png" alt="Genkit slackFlow" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see that one of the tools handles transformation of dates like today or yesterday into date string&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.amazonaws.com%2Fuploads%2Farticles%2F5bi5rsljhqm0j8x6ejqg.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.amazonaws.com%2Fuploads%2Farticles%2F5bi5rsljhqm0j8x6ejqg.png" alt="Date tool" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And result of the date tool gets passed into the &lt;strong&gt;getCustomersForDate&lt;/strong&gt; tool, which simulates an internal API call to return a random number of customers.&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.amazonaws.com%2Fuploads%2Farticles%2Fy28uax4ff3rlfsaejon4.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.amazonaws.com%2Fuploads%2Farticles%2Fy28uax4ff3rlfsaejon4.png" alt="Simulated API call tool" width="800" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And final output. Genkit debugging is super useful when dealing with complicated flows and tool calls, make sure to try it out.&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.amazonaws.com%2Fuploads%2Farticles%2F4hnx4myopno6k286gqk0.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.amazonaws.com%2Fuploads%2Farticles%2F4hnx4myopno6k286gqk0.png" alt="Genkit output" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Deployment
&lt;/h4&gt;

&lt;p&gt;Now you can run $$npm run deploy$$ to deploy the function and get the URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Slack events setup
&lt;/h3&gt;

&lt;p&gt;From the previous step, you should have a URL looking like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://gemini-in-slack-{random-id}-uc.a.run.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Head to your newly created Slack app into the &lt;strong&gt;Event Subscriptions&lt;/strong&gt; section and enable events. This will give you an option to paste your Firebase Function URL there, &lt;strong&gt;make sure to add /events&lt;/strong&gt; to the end of the URL after pasting it, so the complete URL looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://gemini-in-slack-{random-id}-uc.a.run.app/events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2F0r577d35y7ipbenk3ob4.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.amazonaws.com%2Fuploads%2Farticles%2F0r577d35y7ipbenk3ob4.png" alt="Subscribe to events URL" width="800" height="303"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fvsmvnc7zpkpfiuy8c0ql.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.amazonaws.com%2Fuploads%2Farticles%2Fvsmvnc7zpkpfiuy8c0ql.png" alt="URL verification" width="800" height="722"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should get a green checkmark once Slack verifies your endpoint. Below the URL, subscribe to the &lt;strong&gt;app_mention&lt;/strong&gt; event, to start receiving event notifications.&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.amazonaws.com%2Fuploads%2Farticles%2Fujf78y7klodlj89wbv21.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.amazonaws.com%2Fuploads%2Farticles%2Fujf78y7klodlj89wbv21.png" alt="URL verified" width="534" height="74"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;Now it all should be ready to test! Head on to your Slack workspace and choose a channel where you won't disturb anyone, making sure to tag the bot with your query. You should get an in-thread answer. If you didn't change the prompt or tool calls, here are some queries that should work just fine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many customers did we have today?&lt;/li&gt;
&lt;li&gt;What was our revenue yesterday?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And to try the error handling, try asking: "What will be our revenue tomorrow?"&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.amazonaws.com%2Fuploads%2Farticles%2Ff87zx0ak8fohboix5yte.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.amazonaws.com%2Fuploads%2Farticles%2Ff87zx0ak8fohboix5yte.png" alt="Sample conversation" width="644" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Genkit Monitoring
&lt;/h3&gt;

&lt;p&gt;After few conversations, head on to Firebase Genkit in the &lt;a href="https://console.firebase.google.com" rel="noopener noreferrer"&gt;Firebase Console&lt;/a&gt; to see all the logs and traces for your conversations, where you can later debug new calls and see if the tool replies as it should.&lt;br&gt;
You also have an overview of tokens consumed and if something isn't taking too long.&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fn4ainy8ba5z1r8d6yrk7.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.amazonaws.com%2Fuploads%2Farticles%2Fn4ainy8ba5z1r8d6yrk7.png" alt="Firebase Genkit Monitoring" width="800" height="653"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshooting
&lt;/h3&gt;

&lt;p&gt;If you do not receive an answer within a few minutes, make sure to check the Firebase Cloud Function logs to get an idea what went wrong. You might be missing an API key or left out one of the permissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;We went over the basics of how to set up a Slack bot, explained some Genkit concepts and put it all together. I hope you can modify the sample code to something actually useful for your organization and thanks for reading!&lt;/p&gt;

&lt;p&gt;Google Cloud credits are provided for this project. #AISprint&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>gemini</category>
      <category>genkit</category>
    </item>
    <item>
      <title>Understanding Genkit flows with Czech language tricks</title>
      <dc:creator>Denis Valášek</dc:creator>
      <pubDate>Sat, 28 Sep 2024 11:48:18 +0000</pubDate>
      <link>https://dev.to/denisvalasek/understanding-genkit-flows-with-czech-language-tricks-26i3</link>
      <guid>https://dev.to/denisvalasek/understanding-genkit-flows-with-czech-language-tricks-26i3</guid>
      <description>&lt;h3&gt;
  
  
  Intro
&lt;/h3&gt;

&lt;p&gt;Developing with LLMs still carries some risks: they're hard to debug, it's difficult to observe what's happening, and when you're using multiple prompts for a single task, it can quickly become a mess. Let's explore Firebase Genkit to see how it can make things a bit easier. I'll use a practical example of converting text with difficult characters (equations, dates, physics symbols) in Czech to their spoken variants, which will be better handled by Text-to-Speech systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Genkit?
&lt;/h3&gt;

&lt;p&gt;Genkit is an open source framework from Firebase that provides libraries and developer tools to get the most of the various AI and LLM APIs, not just from Google. While it's made by the Firebase team, there are many additional plugins available that enable support for other providers like &lt;a href="https://github.com/TheFireCo/genkit-plugins/tree/main/plugins/anthropic" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt;, &lt;a href="https://github.com/TheFireCo/genkit-plugins/tree/main/plugins/openai" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt; and &lt;a href="https://firebase.google.com/docs/genkit/plugins/ollama" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; simply by switching a line of code. &lt;/p&gt;

&lt;p&gt;A great part of Genkit are also developer tools that add additional observability and debugging options to developing LLM-powered apps, giving you a bit of reassurance in the non-deterministic characteristics that come with building on LLMs. &lt;/p&gt;

&lt;h3&gt;
  
  
  Getting started with Firebase Genkit
&lt;/h3&gt;

&lt;p&gt;Use &lt;a href="https://firebase.google.com/docs/genkit/get-started" rel="noopener noreferrer"&gt;official docs&lt;/a&gt; to set up a new Genkit project. We will be using TypeScript, but Genkit also provides Go support. Make sure to say yes to creating a sample flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;p&gt;Flows are wrapper functions that enable us to tie various tasks together into a single callable function, while giving us observability and easier testing. &lt;/p&gt;

&lt;p&gt;When creating new Genkit project, in the index.ts, you will have sample flow generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Define a simple flow that prompts an LLM to generate menu suggestions.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;menuSuggestionFlow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;menuSuggestionFlow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Construct a request and send it to the model API.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;llmResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Suggest an item for the menu of a &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; themed restaurant`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gemini15Flash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// Handle the response from the model API. In this sample, we just convert&lt;/span&gt;
    &lt;span class="c1"&gt;// it to a string, but more complicated flows might coerce the response into&lt;/span&gt;
    &lt;span class="c1"&gt;// structured output or chain the response into another LLM call, etc.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;llmResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;genkit start&lt;/code&gt; in your project folder to build the project and start the developer UI at &lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;http://localhost:4000&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can see the flow being ready in the left menu under Flows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxmsjbpxaks5k1mqv53j8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxmsjbpxaks5k1mqv53j8.png" alt="Genkit Developer tools - Sample flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's try it out! In the code, we can see that this flow expects just a string input (&lt;code&gt;inputSchema: z.string()&lt;/code&gt;), so providing "sushi" as a restaurant theme should be enough.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fta0m0pacldt7h5ufw77e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fta0m0pacldt7h5ufw77e.png" alt="Genkit Developer tools - Sample flow run"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can also click on the "View trace" button, to see exactly what's happening inside our flow with defined spans. Currently, we only have the model call.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7gmhuuh9csv70ep86ueb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7gmhuuh9csv70ep86ueb.png" alt="Genkit Developer tools - Sample flow traces"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Preparing our prompts
&lt;/h3&gt;

&lt;p&gt;Genkit supports the Dotprompt prompt format via the Dotprompt plugin, let's &lt;a href="https://firebase.google.com/docs/genkit/dotprompt" rel="noopener noreferrer"&gt;install it by following the docs&lt;/a&gt;. Don't forget to add Dotprompt as a plugin when calling the &lt;code&gt;configureGenkit(...)&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Dotprompt enables you to save your prompts as separate files that you can then directly use in your code. This separation helps you keep things clean and manageable as you scale, upgrade prompts etc. &lt;/p&gt;

&lt;p&gt;Opening the Gemini 1.5 Flash model (or other models) within the Genkit UI will give us access to a prompt testing &amp;amp; creation UI. &lt;/p&gt;

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

&lt;p&gt;Feel free to play with the UI and create some sample prompts. When you are done, click "Export prompt" and save the result in the &lt;code&gt;prompts&lt;/code&gt; folder in your project. &lt;/p&gt;

&lt;p&gt;We can modify the .prompt file to accept one or more parameters by adding the input header with a schema. The parameters can then be used inside our prompt text using the handlebars syntax. &lt;/p&gt;

&lt;p&gt;Note: If you don't speak Czech, you might be wondering what's inside the prompts. The first one gives a generic instruction about the role the model is to assume - in this case, a student doing their physics finals. The 2nd one gives various examples about how specific things should be rewritten. For example,  1998 should be written as nine thousand ninety eight, etc. This helps the TTS engine pronounce words correctly in Czech. &lt;/p&gt;

&lt;p&gt;Here is my &lt;code&gt;physics.prompt&lt;/code&gt; including parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vertexai/gemini-1.5-flash&lt;/span&gt;
&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.3&lt;/span&gt;
  &lt;span class="na"&gt;maxOutputTokens&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8192&lt;/span&gt;
  &lt;span class="na"&gt;topK&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;32&lt;/span&gt;
  &lt;span class="na"&gt;topP&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.95&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;role "system"&lt;/span&gt;&lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="s"&gt;Jsi kluk, jmenuješ se AI maturant a právě maturuješ na osmiletém gymnáziu, vytahuješ si téma z fyziky. Tvůj výklad by neměl zabrat déle než jednu minutu, poté budou následovat otázky od komise, které budou navazovat na dané téma a předmět. Na začátku uvítej komisi a pokračuj ve svém výkladu. Pokud dostaneš příklad k vypočítání, buď co nejpřesnější, dávej si pozor na jednotky, nezaokrouhluj na vysoké čísla, je velmi důležité, aby výsledky byly správně. V úvodu zahrň vypočítaný příklad včetně rovnice.&lt;/span&gt;

&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;role "user"&lt;/span&gt;&lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="s"&gt;Tématem je "{{question}}". Co mi o tomto tématu řekneš prosím?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And spoken_text.prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vertexai/gemini-1.5-flash&lt;/span&gt;
&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.4&lt;/span&gt;
  &lt;span class="na"&gt;topK&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;32&lt;/span&gt;
  &lt;span class="na"&gt;maxOutputTokens&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8192&lt;/span&gt;
  &lt;span class="na"&gt;topP&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.95&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;writtenText&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;role "user"&lt;/span&gt;&lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="s"&gt;Prosím převěď následující text tak, aby se dal snadno vyslovit. Mimo jiné, následuj tyto pravidla&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="s"&gt;===PRAVIDLA:===&lt;/span&gt;
&lt;span class="c1"&gt;# Vzorečky piš tak, aby se daly přečíst a vyslovit.&lt;/span&gt;
&lt;span class="s"&gt;===Příklad:===&lt;/span&gt;
&lt;span class="s"&gt;F = m * a&lt;/span&gt;
&lt;span class="s"&gt;===Výsledek:===&lt;/span&gt;
&lt;span class="s"&gt;Vektor síly se rovná hmotnosti tělesa vynásobenou vektorem zrychlení.&lt;/span&gt;
&lt;span class="s"&gt;===Příklad:===&lt;/span&gt; 
&lt;span class="s"&gt;g = G * M / r^2&lt;/span&gt;
&lt;span class="s"&gt;===Výsledek:===&lt;/span&gt;
&lt;span class="s"&gt;Intezita gravitačního pole se rovná gravitační konstata krát hmotnost tělesa děleno vzdáleností tělesa na druhou&lt;/span&gt;
&lt;span class="c1"&gt;# Používej psané číslovky:&lt;/span&gt;
&lt;span class="s"&gt;===Příklad:===&lt;/span&gt;
&lt;span class="s"&gt;20. století&lt;/span&gt;
&lt;span class="s"&gt;===Výsledek:===&lt;/span&gt;
&lt;span class="s"&gt;dvacáté století&lt;/span&gt;
&lt;span class="s"&gt;===Příklad:===&lt;/span&gt;
&lt;span class="s"&gt;0,00027 N/kg&lt;/span&gt;
&lt;span class="s"&gt;===Výsledek:===&lt;/span&gt;
&lt;span class="s"&gt;nula celá nula nula nula dvacet sedm newtonů na kilogram&lt;/span&gt;

&lt;span class="c1"&gt;# Místo znaků jako =, / nebo * používej slovní spojení jako "rovná se", "děleno" nebo "krát".&lt;/span&gt;
&lt;span class="s"&gt;===Příklad:===&lt;/span&gt;
&lt;span class="s"&gt;2 * 3 = &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;
&lt;span class="s"&gt;===Výsledek:===&lt;/span&gt;
&lt;span class="s"&gt;Dva krát tři se rovná šest&lt;/span&gt;

&lt;span class="c1"&gt;# Místo 1., 2., 3., piš "první", "druhý", "třetí" nebo Za prvé, za druhé, za třetí a podobně, v závislosti na kontextu.&lt;/span&gt;
&lt;span class="s"&gt;===Příklad:===&lt;/span&gt;
&lt;span class="s"&gt;1. Místo&lt;/span&gt;
&lt;span class="s"&gt;===Výsledek:===&lt;/span&gt;
&lt;span class="s"&gt;první místo&lt;/span&gt;

&lt;span class="s"&gt;===Příklad:===&lt;/span&gt; 
&lt;span class="s"&gt;1) Věda a výzkum&lt;/span&gt;
&lt;span class="s"&gt;===Výsledek:===&lt;/span&gt;
&lt;span class="s"&gt;Za prvé, věda a výzkum&lt;/span&gt;

&lt;span class="s"&gt;===Příklad:===&lt;/span&gt; 
&lt;span class="s"&gt;Alexandr I.&lt;/span&gt;
&lt;span class="s"&gt;===Výsledek:===&lt;/span&gt;
&lt;span class="s"&gt;Alexandr první&lt;/span&gt;

&lt;span class="s"&gt;=== Konec pravidel ===&lt;/span&gt;
&lt;span class="na"&gt;Následující text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;writtenText&lt;/span&gt;&lt;span class="pi"&gt;}}&lt;/span&gt;

&lt;span class="na"&gt;Převedený text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a quick check, this is how your folder structure should look like now:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Connecting the prompts inside a flow
&lt;/h3&gt;

&lt;p&gt;Let's modify the pregenerated example flow, changing the name and input/output schemas. Genkit uses &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;zod&lt;/a&gt; to define strongly typed schemas enforced even at runtime. &lt;/p&gt;

&lt;p&gt;We also need to use the Dotprompt plugin to import the prompts into our code by calling &lt;code&gt;promptRef&lt;/code&gt; with our prompt file name as a parameter. &lt;/p&gt;

&lt;p&gt;Now we make 2 sepaerate calls to the model with our prompts, chaining them together with the results of the previous one, with the option to also not generate the spoken form at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;physicsPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;promptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;physics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spokenTextPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;promptRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;spoken_text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;studentAnswerFlow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;studentAnswerFlow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;convertToSpokenText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;spokenAnswer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;answerResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;physicsPrompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// If we don't need spoken answer, return only the written answer&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;convertToSpokenText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;answerResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spokenAnswerResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;spokenTextPrompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;writtenText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;answerResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;answerResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;spokenAnswer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;spokenAnswerResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when we load the Genkit UI and open the updated flow, we can already see the newly defined parameters as JSON. Let's populate the &lt;code&gt;question&lt;/code&gt; parameter with  "Mechanika kapalin a plynů" and set &lt;code&gt;convertToSpokenText&lt;/code&gt; to true. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqwrrcehsgb43cgfececk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqwrrcehsgb43cgfececk.png" alt="Updated flow with predefined parameters"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Running the flow, we can see the outputs as we have defined them in the code. We can notice that the outputs are slightly different, because the second prompt is transforming the text to be better understood by the TTS engine. &lt;/p&gt;

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

&lt;p&gt;Opening the traces, we can see both calls being defined. Drilling deeper, we can inspect the individual runs for every prompt, along with timings, to see what's taking up the most time.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01tuav0yz2b2yx6kp006.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01tuav0yz2b2yx6kp006.png" alt="Trace spans timings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6h1a0kbrtznyqheg01an.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6h1a0kbrtznyqheg01an.png" alt="Trace prompt detail"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying flows
&lt;/h3&gt;

&lt;p&gt;Genkit is highly flexible with deployment options - you can deploy to any Node.js environment. But you get the most out of the framework by deploying to Google Cloud Run or Cloud Functions for Firebase . The details for deployment are well described &lt;a href="https://firebase.google.com/docs/genkit/flows#deploying_flows" rel="noopener noreferrer"&gt;in the docs&lt;/a&gt;. I would recommend Cloud Run as it uses a single Cloud Run service for all the flows, keeping the costs low in case you need to deploy multiple flows and keep minInstances: 1 to avoid longer cold starts. &lt;/p&gt;

&lt;p&gt;You can view the complete &lt;a href="https://github.com/DenisVCode/genkit-maturita" rel="noopener noreferrer"&gt;sample project on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading and special thanks to &lt;a href="https://x.com/peterfriese" rel="noopener noreferrer"&gt;Peter Friese&lt;/a&gt; from the Firebase team for helping me out! &lt;/p&gt;

&lt;h6&gt;
  
  
  &lt;em&gt;Google Cloud credits are provided for this project #AISprint&lt;/em&gt;
&lt;/h6&gt;

</description>
      <category>genkit</category>
      <category>firebase</category>
      <category>aisprint</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Learning from my YouTube History: Summarization via Gemini</title>
      <dc:creator>Denis Valášek</dc:creator>
      <pubDate>Mon, 26 Feb 2024 15:08:44 +0000</pubDate>
      <link>https://dev.to/denisvalasek/learning-from-my-youtube-history-summarization-via-gemini-3o6n</link>
      <guid>https://dev.to/denisvalasek/learning-from-my-youtube-history-summarization-via-gemini-3o6n</guid>
      <description>&lt;h3&gt;
  
  
  Intro
&lt;/h3&gt;

&lt;p&gt;We have already probably seen 1000+ videos on YouTube and hopefully gained some knowledge out of them. In this series I would like to explore the possiblities of extracting data from my YouTube history and ending up with personal YT query engine, where through Gemini and embeddings I could converse with all the videos I have watched. &lt;/p&gt;

&lt;h3&gt;
  
  
  Getting data
&lt;/h3&gt;

&lt;p&gt;You can export your YouTube history either using the &lt;a href="https://developers.google.com/data-portability/user-guide/introduction" rel="noopener noreferrer"&gt;Google Data Portability API&lt;/a&gt;, if you want to download your data more often and convintiently or do it manually using &lt;a href="https://takeout.google.com" rel="noopener noreferrer"&gt;Google Takeout&lt;/a&gt;. Formatting should be the same in both cases, if you select JSON.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subtitles
&lt;/h3&gt;

&lt;p&gt;Since downloading all the videos and getting the transcription from better models (As far as I know, YouTube doesn't recreate the subtitles with updated STT models) would be probably against ToS, we have to use the subtitles that YouTube provides as a content of the video. YouTube has &lt;a href="https://developers.google.com/youtube/v3/docs/captions" rel="noopener noreferrer"&gt;official API&lt;/a&gt; that we can use to download captions for our use-case. &lt;/p&gt;

&lt;p&gt;Our data export contains YouTube video IDs, then we can use the &lt;a href="https://developers.google.com/youtube/v3/docs/captions/list" rel="noopener noreferrer"&gt;list endpoint&lt;/a&gt; to get all subtitles avaliable for that video. Sometimes, manually uploaded subtitles are avaliable, which is better option than the STT machine transcription, as they better separate sentences and speakers, which would yield improved results. &lt;/p&gt;

&lt;p&gt;Example request:&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="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'https://youtube.googleapis.com/youtube/v3/captions?part=id%2Csnippet&amp;amp;videoId=yWBzsBaU-Os&amp;amp;key=[YOUR_API_KEY]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer [YOUR_ACCESS_TOKEN]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Accept: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--compressed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example response:&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;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"youtube#captionListResponse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"etag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"09LrB4i7CoUwNzOLNJE6CCffKPU"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"youtube#caption"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"etag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"oO2GJpAhG7JcHcqx3d2xYnMumA8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"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;"AUieDabcbEXa0PfMheqVCKM2A_H-JMA8hrRDlOWDlhTfZnBcA7k"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"snippet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"videoId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yWBzsBaU-Os"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"lastUpdated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-25T01:43:01.37359Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"trackKind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"asr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"audioTrackType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unknown"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"isCC"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"isLarge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"isEasyReader"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"isDraft"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"isAutoSynced"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"serving"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we get the captions ID, we can use the Download endpoint to get the subtitle file.&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="se"&gt;\ &lt;/span&gt; &lt;span class="s1"&gt;'https://youtube.googleapis.com/youtube/v3/captions/AUieDabcbEXa0PfMheqVCKM2A_H-JMA8hrRDlOWDlhTfZnBcA7k?key=[YOUR_API_KEY]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer [YOUR_ACCESS_TOKEN]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Accept: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--compressed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We repeat the process for all the videos and store the results. &lt;/p&gt;

&lt;h3&gt;
  
  
  Summarization
&lt;/h3&gt;

&lt;p&gt;We can use Gemini to create a summary from the subtitles and title that we can store alongside the embeddings and subtitles. Let's do it!&lt;/p&gt;

&lt;h4&gt;
  
  
  Google Gemini via Vertex AI
&lt;/h4&gt;

&lt;p&gt;Gemini can run in a few different enviroments, today, we will use Gemini via Google Vertex AI, as it's easily accessible even in Europe. If you are interested in learning the differences between the versions of Gemini, check out &lt;a href="https://code.iaflw.com/2024/02/gemini-versus-gemini-understanding.html" rel="noopener noreferrer"&gt;this article&lt;/a&gt; made by fellow GDE Allen Firstenberg. &lt;/p&gt;

&lt;p&gt;Prompt that we will use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I will give you a video transcription, please return a 2-4 sentence summary of the text and few tags that represent the video well in JSON format with fields summary and tags as array of strings. 

Title: ${VIDEO_TITLE}
Transcription: ${TRANSCRIPTION}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/gemini" rel="noopener noreferrer"&gt;Getting started guide&lt;/a&gt; for Gemini describes the details pretty well, building of it, let's build example request:&lt;/p&gt;

&lt;p&gt;Note: &lt;em&gt;The curl command requires GCP project and gcloud installed. Follow the starter guide if you need to do that.&lt;/em&gt;&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;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://us-central1-aiplatform.googleapis.com/v1/projects/&lt;span class="o"&gt;{&lt;/span&gt;YOUR_GOOGLE_PROJECT_ID&lt;span class="o"&gt;}&lt;/span&gt;/locations/us-central1/publishers/google/models/gemini-1.0-pro:streamGenerateContent &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer $(gcloud auth print-access-token)'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json; charset=utf-8'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
  "contents": {
    "role": "user",
    "parts": {
        "text": "I will give you a video transcription, please return a 2-4 sentence summary of the text and few tags that represent the video well in JSON format with fields summary and tags as array of strings. \nTitle: Turning Old Sawmill Blades into Knives | How It’s Made | Science Channel\nTranscription: a sawmill blade lasts 5 to 10 years but when its Jagged edges wear thin it can'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'t cut logs anymore luckily the carbon steel can be salvaged a computerized high-press water tool cuts into a stack of three Sawmill blades in doing so it carves out numerous knife blade blanks the computerized tool also Cuts holes for the handles and a tab for attaching the knife sheath next the blades are transferred to a vibratory tumbler the tumbler is filled with triangular ceramic stones and a soapy solution for several hours the vibrating Stones smooth and clean the blades once complete a technician secures the blades in a fixture with screws the screws hold the blades down flat and lock them in position for the next step"
    }
  },
  "generation_config": {
    "temperature": 0.2,
    "topP": 0.8,
    "topK": 40
  }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After merging array of responses, we get a result like this:&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;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Old sawmill blades are repurposed into knives by cutting them into blanks using a high-press water tool. The blanks are then tumbled in a vibratory tumbler with ceramic stones to smooth and clean them. Finally, the blades are secured in a fixture and screws are used to hold them in place for the next step in the knife-making process."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Sawmill blades"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Knives"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"High-press water tool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Vibratory tumbler"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Ceramic stones"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Fixture"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Screws"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we can see that Gemini managed to parse the raw transcription quite well, even though it didn't even have separated senteces. We can save the summary and tags for further processing.&lt;/p&gt;

&lt;p&gt;Watch out when running this on a large number of videos or longer videos (15 min+), as it can consume tokens very quickly. I have used &lt;a href="https://www.youtube.com/watch?v=GBfkkR5hM6U" rel="noopener noreferrer"&gt;this YT shorts video&lt;/a&gt; as it can fit nicely into the article. &lt;/p&gt;

&lt;p&gt;Thank you for reading and next time we will look at how we can use Google Gemini to create embeddings from the subtitles. &lt;/p&gt;

&lt;p&gt;Disclaimer: Google Cloud credits are provided for this project #GeminiSprint&lt;/p&gt;

</description>
      <category>geminisprint</category>
      <category>googleai</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Sorting NFT Art with MakerSuite and PALM API</title>
      <dc:creator>Denis Valášek</dc:creator>
      <pubDate>Sat, 30 Sep 2023 16:11:18 +0000</pubDate>
      <link>https://dev.to/denisvalasek/sorting-nft-art-with-makersuite-and-palm-api-4262</link>
      <guid>https://dev.to/denisvalasek/sorting-nft-art-with-makersuite-and-palm-api-4262</guid>
      <description>&lt;p&gt;At &lt;a href="https://rc.xyz/" rel="noopener noreferrer"&gt;rc.xyz&lt;/a&gt; we sort out hundereds of thousands of NFT Artworks so the artists have nice and clean portfolios of their work. However, sometimes it's hard to convert the logic into the code, so let's use the help of Google MakerSuite and PALM API! &lt;/p&gt;

&lt;p&gt;We won't dive deep into what &lt;a href="https://www.youtube.com/watch?v=Ce1AOchQMzA" rel="noopener noreferrer"&gt;MakerSuite&lt;/a&gt; or &lt;a href="https://www.youtube.com/watch?v=yAANQypgOo8" rel="noopener noreferrer"&gt;PALM&lt;/a&gt; is, if you are interested, please watch the linked introduction videos!&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxr9n7uyjgn3m8yfcbz4m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxr9n7uyjgn3m8yfcbz4m.png" alt="MakerSuite intro screen" width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MakerSuite offers several ways for interacting with the API, for our use-case, we will use the &lt;strong&gt;Data prompt&lt;/strong&gt;, since it gives us a UI to create a prompt with few examples more easily.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First version of our prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;You are a category classifier, your job is to decide whether the items match together and if so, generate suitable category name. You output JSON with value valid: boolean and optionally name: string if valid is true.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We are using few tricks to make our prompt perform better:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Giving the LLM instructions about what it is supposed to do&lt;/li&gt;
&lt;li&gt;Describing desired output structure (JSON with types like string and boolean)&lt;/li&gt;
&lt;li&gt;Describing what choices it should made&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Examples:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ou0zywyjfuvrphwxr3d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ou0zywyjfuvrphwxr3d.png" alt="Examples text is provided in the prompt link at the end" width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tip: You can import/export examples right from the MakerSuite, under the Actions button&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ln32ulnlwcz9fgv6sey.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ln32ulnlwcz9fgv6sey.png" alt="MakerSuite action button" width="752" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;MakerSuite enables us to test our prompt with multiple test fields at once, so we don't have to call the API again and again, let's try it out with some cases we have not used in the testing data. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqa3hzw9h73x6lstxmskb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqa3hzw9h73x6lstxmskb.png" alt="Test cases" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And after running the prompt:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F15jacw630wjsbpfk94on.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F15jacw630wjsbpfk94on.png" alt="Finished generation" width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which is in line with what we expect, great! &lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;MakerSuite offers you to export the code snippet right from the web UI, which includes the full formatted prompt along will the other model settings. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftonwd315meta46u5tznc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftonwd315meta46u5tznc.png" alt="Code export" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While the JS snippet suggest installing helper packages, it's still fully possible to use just fetch with the API key, since there isn't any complicated auth process as with most of the Google APIs. Thanks to minimal dependencies, it can run anywhere in your backend, like in &lt;a href="https://firebase.google.com/docs/functions/" rel="noopener noreferrer"&gt;Firebase Cloud Functions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As with any Generative AI stuff, we highly recommend adding human in the loop to verify that the results are valid and revert them if not. &lt;/p&gt;

&lt;p&gt;You can view the full prompt thanks to &lt;a href="https://makersuite.google.com/app/prompts?state=%7B%22ids%22%3A%5B%2214gdZpSKg5X_FtNHu43UuxRrz1j0-9TtJ%22%5D%2C%22action%22%3A%22open%22%2C%22userId%22%3A%22101375204923275298044%22%2C%22resourceKeys%22%3A%7B%7D%7D&amp;amp;usp=sharing" rel="noopener noreferrer"&gt;MakerSuite sharing here.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>ai</category>
    </item>
    <item>
      <title>Solving missing System Entities in Actions Builder: Community Types!</title>
      <dc:creator>Denis Valášek</dc:creator>
      <pubDate>Fri, 24 Jul 2020 09:44:40 +0000</pubDate>
      <link>https://dev.to/denisvalasek/solving-missing-system-entities-in-actions-builder-community-types-1234</link>
      <guid>https://dev.to/denisvalasek/solving-missing-system-entities-in-actions-builder-community-types-1234</guid>
      <description>&lt;p&gt;If you have tried the new tool for building Actions on Google, the Actions Builder, you may have noticed a few things missing (&lt;a href="https://dev.to/denisvalasek/actions-builder-vs-dialogflow-feature-availability-4pho"&gt;feature comparison of Dialogflow/Actions Builder&lt;/a&gt;). One of them being a large collection of System Entities - predefined data sets that enable you to extract specific data from user queries. &lt;/p&gt;

&lt;p&gt;With Actions Builder, you have the following Types (entities) available: &lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fno82vy0w5vdkfzbuccdt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fno82vy0w5vdkfzbuccdt.jpg" alt="Actions on Google" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For comparison, &lt;a href="https://cloud.google.com/dialogflow/docs/reference/system-entities" rel="noopener noreferrer"&gt;Dialogflow contains 30+ system entities&lt;/a&gt; in different languages. &lt;/p&gt;

&lt;p&gt;But one of the cool features of Actions Builder is the ability to relatively quickly import new Types/Intents using &lt;a href="https://developers.google.com/assistant/actionssdk/gactions" rel="noopener noreferrer"&gt;gactions CLI&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;So I decided to use that to create &lt;a href="https://github.com/DenisVCode/aog-community-types" rel="noopener noreferrer"&gt;open collection of types&lt;/a&gt;, created by the community, for the community! You will find the list of types along with instructions on how to include them in your new Action.&lt;/p&gt;

&lt;p&gt;Contributions are highly appreciated! &lt;/p&gt;

&lt;p&gt;Find &lt;a href="https://github.com/DenisVCode/aog-community-types" rel="noopener noreferrer"&gt;Actions Builder Community Types on Github&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Enjoy #AoGDevs!&lt;/p&gt;

</description>
      <category>actionsbuilder</category>
      <category>actionsongoogle</category>
      <category>googleassistant</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Actions Builder vs. Dialogflow: Feature availability</title>
      <dc:creator>Denis Valášek</dc:creator>
      <pubDate>Sat, 11 Jul 2020 16:46:00 +0000</pubDate>
      <link>https://dev.to/denisvalasek/actions-builder-vs-dialogflow-feature-availability-4pho</link>
      <guid>https://dev.to/denisvalasek/actions-builder-vs-dialogflow-feature-availability-4pho</guid>
      <description>&lt;p&gt;Google had recently released new tool for building new Actions - &lt;a href="https://developers.googleblog.com/2020/06/announcing-actions-builder-actions-sdk.html" rel="noopener noreferrer"&gt;Actions Builder&lt;/a&gt;. It replaces Dialogflow and is a default tool to create new Actions from now on, it also comes with a new &lt;a href="https://github.com/actions-on-google/assistant-conversation-nodejs" rel="noopener noreferrer"&gt;SDK library&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I recommend you try it out with new &lt;a href="https://codelabs.developers.google.com/?cat=Assistant" rel="noopener noreferrer"&gt;Actions on Google Codelabs&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;However, since it was released only a few weeks ago, many features we are used to from Dialogflow are missing. This could be a breakpoint for you if you are deciding what to use for your next Action. &lt;/p&gt;

&lt;p&gt;I created &lt;a href="https://docs.google.com/spreadsheets/d/1LTsmi1cekrSgNdWMbVEZ9YYeIMYINtB4K-lGN1m08Yo/edit#gid=0" rel="noopener noreferrer"&gt;comparison spreadsheet&lt;/a&gt; with a list of features and their availability. It will be updated when the feature availability changes or when there are new features for each platform. &lt;/p&gt;

&lt;p&gt;If the feature is marked as unavailable, that doesn't mean it will stay that way. Actions Builder is still in active development and the table reflects the current state. It should help you to decide if you are starting a new project today, but features could change when you create the next one, so I recommend bookmarking the spreadsheet for future use :) &lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.google.com/spreadsheets/d/1LTsmi1cekrSgNdWMbVEZ9YYeIMYINtB4K-lGN1m08Yo/edit" rel="noopener noreferrer"&gt;Actions on Google platform comparison spreadsheet&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks &lt;a href="https://twitter.com/afirstenberg" rel="noopener noreferrer"&gt;@afirstenberg&lt;/a&gt; for helping me with this!&lt;br&gt;
Enjoy #AoGDevs!&lt;/p&gt;

</description>
      <category>actionsongoogle</category>
      <category>dialogflow</category>
      <category>actionsbuilder</category>
      <category>googleassistant</category>
    </item>
    <item>
      <title>Track time you spent on your projects using your #Voice</title>
      <dc:creator>Denis Valášek</dc:creator>
      <pubDate>Sat, 27 Jun 2020 17:19:43 +0000</pubDate>
      <link>https://dev.to/denisvalasek/track-time-you-spent-on-your-projects-using-your-voice-k7k</link>
      <guid>https://dev.to/denisvalasek/track-time-you-spent-on-your-projects-using-your-voice-k7k</guid>
      <description>&lt;h3&gt;
  
  
  Why?
&lt;/h3&gt;

&lt;p&gt;If you are a freelance developer, you probably have to track your time for the projects you do - or perhaps you just want to know, how long did you work on your side project, that you never finished. But keeping some app open all the time and keeping the entries in sync is a bit of a hassle and distracting. I was facing the same problem, so I decided to solve it with my favorite interface - Voice! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff3dlj8luvb5z7izl4mz0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff3dlj8luvb5z7izl4mz0.jpg" alt="Alt Text" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution
&lt;/h3&gt;

&lt;p&gt;I have created Action for Google Assistant called Tracker for Toggl. Why Toggl? Because while voice is great, voice app itself is not enough for timetracking, you probably want web access, fancy reports and mobile app too. Toggl is a time-tracking app, which is available for free, so I picked it as a "backend" for the Action. &lt;/p&gt;

&lt;p&gt;How does it work? Google Assistant offers &lt;a href="https://developers.google.com/assistant" rel="noopener noreferrer"&gt;Actions on Google&lt;/a&gt; platform, which you can use to build your own conversational experiences, where you take full control over what the user says and how you respond. &lt;br&gt;
That means you have to understand, what the user says. I use Dialogflow to do the NLU and Firebase Cloud Functions for fulfillment and web hosting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Showcase
&lt;/h3&gt;

&lt;p&gt;There are several ways, how you can try the Action out! If you have a Google Assistant enabled device - like Google Nest Mini or Home Hub, you can say "Hey Google, talk to Tracker for Toggl", which will get you talking with the Action! Or if you are on the computer, use the &lt;a href="https://assistant.google.com/services/a/uid/000000a477904132" rel="noopener noreferrer"&gt;Assistant Directory&lt;/a&gt; (Google Play version for voice apps) to send it easily to your phone or smart speaker. &lt;/p&gt;

&lt;p&gt;At the launch, you are welcomed with initial introductions and an opportunity to sign in with your Toggl API Token, which is used for communication between Action and Toggl. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Feaqnnei2ilqsp3ag9ggt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Feaqnnei2ilqsp3ag9ggt.png" alt="Welcome message" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, if you are using a smart speaker or display, you will be redirected using notification to perform the sign in using web popup. You enter the token, it will verify that it is actually token for your account and hurray, back to the Action! Don't worry, you have to sign in only once.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmst6bw2qd4bjhrj6qmwn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmst6bw2qd4bjhrj6qmwn.jpg" alt="Login page" width="437" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can access all features that the Action has to offer + your previous entries, projects, and workspaces are also available!&lt;/p&gt;

&lt;p&gt;The simplest thing you can do is to start tracking, lets try it out by saying "Start tracking Writing an article for project Toggl Action".&lt;/p&gt;

&lt;p&gt;Several things have to happen here. First, the NLU has to recognize the intent itself - to start new time tracking. After that, there are 2 Slots to extract. First is the name of the task "Writing an article" and second is "Toggl Action", which will get matched to users projects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fc8vkihrut5rntev70xca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fc8vkihrut5rntev70xca.png" alt="Success!" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that tracking is running, we can confirm in Toggl web app that it's working! So you can combine webapp and the Action, whenever you need it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1fk5pwr16mm21tw3j9t1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1fk5pwr16mm21tw3j9t1.jpg" alt="Sync with Toggl" width="744" height="66"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And we can easily stop it, by saying "Hey Google, ask Tracker for Toggl to stop tracking" anytime. &lt;/p&gt;

&lt;p&gt;You can also get reports, so for example, if you want to know how long did you work today, you can just ask.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbcpbbh437uektodu8pse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbcpbbh437uektodu8pse.png" alt="Report today" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, you can choose different time intervals or even filter by specific projects. Let's follow up by saying "And past month?".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6iunb20uno5hsff1gtve.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6iunb20uno5hsff1gtve.png" alt="Month report" width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we have a wider variety of tasks, so we get a nice sum up. You can filter this more with projects and workspaces. &lt;/p&gt;

&lt;p&gt;Few other things you can do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hey Google, ask Tracker for Toggl to start tracking Testing (go right to the task)&lt;/li&gt;
&lt;li&gt;Continue last tracking &lt;/li&gt;
&lt;li&gt;Get name and project for current tracking&lt;/li&gt;
&lt;li&gt;Set description/project/tags after the tracking has started&lt;/li&gt;
&lt;li&gt;Get help any time, to get you back on track&lt;/li&gt;
&lt;li&gt;and some easter eggs.. :)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can check out the full feature list at the &lt;a href="https://tracker.valasek.dev/" rel="noopener noreferrer"&gt;Tracker for Toggl web&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;The project is still in the early phase and I would appreciate any feedback, thanks for reading!&lt;/p&gt;

</description>
      <category>voice</category>
      <category>showdev</category>
      <category>actionsongoogle</category>
      <category>dialogflow</category>
    </item>
    <item>
      <title>Actions on Google VS Code Extension</title>
      <dc:creator>Denis Valášek</dc:creator>
      <pubDate>Wed, 27 Feb 2019 19:36:48 +0000</pubDate>
      <link>https://dev.to/denisvalasek/actions-on-google-vs-code-extension-4j68</link>
      <guid>https://dev.to/denisvalasek/actions-on-google-vs-code-extension-4j68</guid>
      <description>&lt;p&gt;You know it, you got a new idea, so you hurry up to get that idea to life, but now you have to do all that boring (or interesting :)) stuff again. Create a new project, install node_modules, remember what was the basic project structure and then finally start writing the code. Which, some of the time, can be repetitive too.&lt;/p&gt;

&lt;p&gt;Unfortunately, I can’t help you with most of the work I talked about, but I hope to help you with some of it :) I got tired of writing the same stuff over and over, so I have looked into the &lt;a href="https://code.visualstudio.com/docs/editor/userdefinedsnippets" rel="noopener noreferrer"&gt;VS Code snippets&lt;/a&gt;. Since I wanted to ease my Actions on Google development, I looked if some snippets are available in VS Code Marketplace. To my surprise, there weren’t any! So I decided to create them myself and share with the community!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz74yxfc9gk3n64cay7ki.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz74yxfc9gk3n64cay7ki.gif" width="800" height="583"&gt;&lt;/a&gt;A faster way to start a new project&lt;/p&gt;

&lt;p&gt;Table of current snippets is below and more are coming soon. If you already know what’s missing or what you want to be added, feel free to let me know or send a&lt;a href="https://github.com/DenisVCode/Actions-on-Google-Snippets-VS-Code" rel="noopener noreferrer"&gt;pull request on Github&lt;/a&gt; :) Currently supporting JavaScript and TypeScript.&lt;/p&gt;

&lt;p&gt;You can find it &lt;a href="https://marketplace.visualstudio.com/items?itemName=DenisV.actions-on-google-snippets" rel="noopener noreferrer"&gt;here&lt;/a&gt; or search for the &lt;strong&gt;Actions on Google snippets&lt;/strong&gt; in VS Marketplace! Happy coding!&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6s9nkph4gdxikuohhzo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6s9nkph4gdxikuohhzo.gif" width="1144" height="834"&gt;&lt;/a&gt;New intent&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>vscode</category>
      <category>javascript</category>
      <category>actionsongoogle</category>
    </item>
  </channel>
</rss>
