<?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: dllewellyn</title>
    <description>The latest articles on DEV Community by dllewellyn (@dllewellyn).</description>
    <link>https://dev.to/dllewellyn</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%2F1637493%2Fe3f47550-60f5-406e-b6ac-3f1a4f17a27b.png</url>
      <title>DEV Community: dllewellyn</title>
      <link>https://dev.to/dllewellyn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dllewellyn"/>
    <language>en</language>
    <item>
      <title>Flame Shield: Protect Your Firebase Budget</title>
      <dc:creator>dllewellyn</dc:creator>
      <pubDate>Tue, 25 Feb 2025 18:55:27 +0000</pubDate>
      <link>https://dev.to/dllewellyn/flame-shield-protect-your-firebase-budget-2mea</link>
      <guid>https://dev.to/dllewellyn/flame-shield-protect-your-firebase-budget-2mea</guid>
      <description>&lt;h2&gt;
  
  
  Stop Unexpected Firebase Costs with Flame Shield
&lt;/h2&gt;

&lt;p&gt;Unexpected Firebase costs can quickly spiral out of control, especially as usage scales. Without strict budget monitoring, developers often find themselves dealing with costly surprises. This is where Flame Shield comes in.&lt;/p&gt;

&lt;p&gt;Flame Shield is a powerful yet simple tool designed to help you manage your Firebase project costs. By setting strict monthly spending limits and enabling an automatic "kill switch," you can prevent unexpected expenses and maintain better budget control.&lt;/p&gt;

&lt;p&gt;This guide will walk you through setting up Flame Shield and setting up a "kill switch" - a hard limit after which your billing account will be disconnected &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Because Flames Shield relies on google billing updates, there is a delay between your billing trigger and the notification - so it can go over the billing limit&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Get Started with Flame Shield
&lt;/h2&gt;

&lt;p&gt;Head over to &lt;a href="https://flamesshield.com" rel="noopener noreferrer"&gt;https://flamesshield.com&lt;/a&gt; to begin protecting your Firebase budget today!&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Set Up Flame Shield
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Add Your Firebase Project
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open the Flame Shield app. &lt;a href="https://app.flamesshield.com" rel="noopener noreferrer"&gt;Flames Shield app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Tap the &lt;code&gt;+&lt;/code&gt; button to add a new project.&lt;/li&gt;
&lt;li&gt;Enter your Firebase Project ID and tap &lt;code&gt;Add Project.&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. Re-authenticate Your Account
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;After adding your project, tap &lt;code&gt;Re-authenticate.&lt;/code&gt; This ensures that Flame Shield can set up necessary alerts and protect your account. &lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Under the hood, we are requesting enhanced permissions temporarily to set up the necessary access to your billing acount&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Review the requested permissions and tap &lt;code&gt;Re-authenticate&lt;/code&gt; again.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select your Google account and grant access.&lt;/p&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%2Fview-documentation-2ucntopmaa-uc.a.run.app%2F%2Fimages%2F9c81f17729cd43008070548ee4aa1b1b4b5d03890c208df83a39d00b2b412f44" 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%2Fview-documentation-2ucntopmaa-uc.a.run.app%2F%2Fimages%2F9c81f17729cd43008070548ee4aa1b1b4b5d03890c208df83a39d00b2b412f44" alt="Re-authenticate your account" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Set Up Billing Protection
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;On the Dashboard, tap &lt;code&gt;Setup Billing Protection.&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter your desired monthly spending limit and tap &lt;code&gt;Activate Billing Protection.&lt;/code&gt; You can update this limit at any time.&lt;/p&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%2F6qv3x902tgya7eny674a.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%2F6qv3x902tgya7eny674a.png" alt=" " width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Kill Switch
&lt;/h2&gt;

&lt;p&gt;It's generally a good idea to test your kill-switch at a less stressful time than 3am to make sure you know what the process is to re-connect. &lt;/p&gt;

&lt;p&gt;Flame Shield’s &lt;code&gt;kill switch&lt;/code&gt; automatically suspends your project if your spending exceeds the set limit. Here’s how to test this feature:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;On the Dashboard, locate the &lt;code&gt;Service Running&lt;/code&gt; status and tap &lt;code&gt;Test Kill Switch.&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confirm that you want to proceed with the test.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Flame Shield will simulate an overspending scenario, and you’ll receive a &lt;code&gt;Billing Account Detached&lt;/code&gt; alert.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To re-enable your project, tap &lt;code&gt;Re-enable Billing Account.&lt;/code&gt;&lt;/p&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%2Fj9mdp2nqxb28wyic7zdn.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%2Fj9mdp2nqxb28wyic7zdn.png" alt=" " width="800" height="781"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Current Spend
&lt;/h2&gt;

&lt;p&gt;Flame Shield allows you to track your project’s expenses directly from the Dashboard. The &lt;strong&gt;Current Spend&lt;/strong&gt; section provides an overview of your usage and ensures you stay within budget.&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%2Fq0j02tdjsztsm0sc8qaq.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%2Fq0j02tdjsztsm0sc8qaq.png" alt=" " width="800" height="712"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Spend data updates &lt;strong&gt;roughly every hour&lt;/strong&gt; as alerts come in from Google.&lt;/li&gt;
&lt;li&gt;Notifications are sent out when:

&lt;ul&gt;
&lt;li&gt;Billing limits are reached.&lt;/li&gt;
&lt;li&gt;The kill switch is no longer active (which can happen for various reasons). It’s essential to ensure that it remains active at all times.&lt;/li&gt;
&lt;li&gt;The kill switch is triggered, indicating that spending has exceeded the set limit.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;By keeping an eye on the Current Spend section and ensuring the kill switch remains active, you can proactively manage your Firebase budget and avoid unexpected charges.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some other good things
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You can configure alerting - this will notify you when your billing limit is at 50%, when your 'ping' is down and when your kill-switch has kicked in&lt;/li&gt;
&lt;li&gt;Ping - there are all sorts of ways that the service could stop working. You might delete the pub/sub, change the billing account or otherwise change the permissions. We ping the system every hour to make sure that kill-switch is always operational when you need it (and let you know what action to take is not)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&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%2Fleey2hmcnlu1j9py8v00.png" alt=" " width="800" height="753"&gt;
&lt;/h2&gt;

&lt;p&gt;By following these steps, you can efficiently manage your Firebase project costs and prevent unexpected overspending. Get started today at &lt;a href="https://flamesshield.com" rel="noopener noreferrer"&gt;https://flamesshield.com&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Prevent Firebase Runaway Costs</title>
      <dc:creator>dllewellyn</dc:creator>
      <pubDate>Thu, 06 Feb 2025 18:34:36 +0000</pubDate>
      <link>https://dev.to/dllewellyn/how-to-prevent-firebase-runaway-costs-150h</link>
      <guid>https://dev.to/dllewellyn/how-to-prevent-firebase-runaway-costs-150h</guid>
      <description>&lt;p&gt;Firebase is an incredibly powerful platform that lets developers build robust applications quickly. However, users are rightly worried about the eye-watering costs, which are usually unexpected, and the fear of hitting that kind of bill puts off a lot of people for whom Firebase is a brilliant tool.&lt;/p&gt;

&lt;p&gt;In this post, we’ll explore the main Firebase services that tend to incur the highest costs, discuss the events that can cause sudden billing spikes, and share strategies for avoiding these Firebase bills, helping you keep your spending under control.&lt;/p&gt;

&lt;p&gt;Check out the full post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://flamesshield.com/blog/how-to-prevent-firebase-runaway-costs/" rel="noopener noreferrer"&gt;How to Prevent Firebase Runaway Costs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Firebase authentication - Best practices for password requirements</title>
      <dc:creator>dllewellyn</dc:creator>
      <pubDate>Fri, 17 Jan 2025 07:43:37 +0000</pubDate>
      <link>https://dev.to/dllewellyn/firebase-authentication-best-practices-for-password-requirements-1ma9</link>
      <guid>https://dev.to/dllewellyn/firebase-authentication-best-practices-for-password-requirements-1ma9</guid>
      <description>&lt;p&gt;While building out an up-coming security and compliance dashboard for Firebase, some of the rules we  looked at were around authentication settings in Firebase which are 'insecure' - we found a fair few that are defaults which was surprising!&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>security</category>
    </item>
    <item>
      <title>How to evaluate the safety and security of LLM Applications?</title>
      <dc:creator>dllewellyn</dc:creator>
      <pubDate>Wed, 18 Dec 2024 18:13:39 +0000</pubDate>
      <link>https://dev.to/dllewellyn/how-to-evaluate-the-safety-and-security-of-llm-applications-5d2f</link>
      <guid>https://dev.to/dllewellyn/how-to-evaluate-the-safety-and-security-of-llm-applications-5d2f</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmwrto8rch3dh3zlngiq5.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%2Fmwrto8rch3dh3zlngiq5.png" alt=" " width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've written a guide on essentially how to test LLM apps for security and safety. Looking forward to hearing what you think! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://prompt-shield.com/blog/llm-app-security-evaluation/" rel="noopener noreferrer"&gt;How to evaluate the safety and security of LLM Applications?&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Build a serverless AI App with Cloud run, Python, Gemini and Vertex AI</title>
      <dc:creator>dllewellyn</dc:creator>
      <pubDate>Wed, 25 Sep 2024 11:17:44 +0000</pubDate>
      <link>https://dev.to/dllewellyn/build-a-serverless-ai-app-with-cloud-run-python-gemini-and-vertex-ai-166f</link>
      <guid>https://dev.to/dllewellyn/build-a-serverless-ai-app-with-cloud-run-python-gemini-and-vertex-ai-166f</guid>
      <description>&lt;p&gt;&lt;strong&gt;This is part 1 in a series!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Show me the code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/dllewellyn/hello-gemini" rel="noopener noreferrer"&gt;https://github.com/dllewellyn/hello-gemini&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;In this series of tutorials, we’re going to use a suite of google tools — AI, cloud and dev — in order to create and deploy an AI powered application. We’ll steer clear on chatbots, because they are a tedious in the extreme and focus on building something more interesting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a “Hello World” Flask Application with Google IDX and the Gemini API
&lt;/h3&gt;

&lt;p&gt;To get started, we’re going to use the ‘hello world’ IDX — Gemini and flask application. This gives us a really quick way to get setup with some AI tooling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project Setup
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Navigate to Google IDX:&lt;/strong&gt; Begin by accessing the Google IDX platform in your web browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select the Gemini API Template:&lt;/strong&gt; From the IDX welcome screen, locate and click on the “Gemini API” template under the “Start something new with a template” section.&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%2Fizm4sjwti2krduzw612l.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%2Fizm4sjwti2krduzw612l.png" alt="https://cdn-images-1.medium.com/max/800/1*3UbWMkF7ijeiRDG11z7KDw.png" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Configure New Workspace:&lt;/strong&gt; A “New Workspace” window will appear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name your workspace:&lt;/strong&gt; I’ve just called it “hello-gemini.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment:&lt;/strong&gt; Choose the “Python Web App (Flask)” option from the dropdown menu.&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%2F9llqms214pjvsti0xo0s.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%2F9llqms214pjvsti0xo0s.png" alt="https://cdn-images-1.medium.com/max/800/1*s9j9Z4H7RirlczOCKohNiw.png" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create Workspace:&lt;/strong&gt; Once configured, click the “Create” button to initialise the workspace creation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Await Setup Completion:&lt;/strong&gt; IDX will set up the necessary environment for your Flask application.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With your workspace ready, we’ve got a basic ‘gemini’ application&lt;/p&gt;

&lt;h3&gt;
  
  
  Looking through the Hello World application
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Obtain a Gemini API Key
&lt;/h3&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%2Fdrpfgk86k33xtkh3438x.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%2Fdrpfgk86k33xtkh3438x.png" alt="https://cdn-images-1.medium.com/max/800/1*c6tBFPL_FQN9mSHMu84AiQ.png" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before you begin, you’ll need an API key to access the Gemini API.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visit the &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;Google Cloud Console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Navigate to the ‘API Keys’ section.&lt;/li&gt;
&lt;li&gt;Click ‘Create API Key’.&lt;/li&gt;
&lt;li&gt;Choose an existing Google Cloud project or create a new one.&lt;/li&gt;
&lt;li&gt;Copy the generated API key. &lt;strong&gt;Remember to store your API key securely!&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Set up the Flask Application
&lt;/h3&gt;

&lt;p&gt;Check the existing app in &lt;code&gt;main.py&lt;/code&gt; — update this with your actual API key.&lt;/p&gt;

&lt;p&gt;This is the basic setup of the gemini API&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.generativeai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;

&lt;span class="c1"&gt;# Replace 'YOUR_API_KEY' with your actual API key
&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Check theHTML Template
&lt;/h3&gt;

&lt;p&gt;Take a look in &lt;code&gt;index.html&lt;/code&gt;to serve as the front-end for your web application. This template will display images of baked goods, an input field for the prompt, and a results section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Baking with the Gemini API&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Baking with the Gemini API&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"images/baked-good-1.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Baked Good 1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"images/baked-good-2.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Baked Good 2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"images/baked-good-3.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Baked Good 3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"prompt"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Provide an example recipe for the baked goods in:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"prompt"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"prompt"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"generateRecipe()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Go&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"results"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Results will appear here&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll see three images on the screen and a prompt input.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define Flask Routes and Functions
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;main.py&lt;/code&gt; file, define the routes and functions to handle requests from the front-end and interact with the Gemini API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;index.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/generate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_api&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&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="n"&gt;req_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req_body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GenerativeModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;req_body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data: %s&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&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="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text/event-stream&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&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;In this block of code, you’ll notice that we take the ‘model’ from the input, and the ‘contents’ which have been fired to the API from javascript.&lt;/p&gt;

&lt;p&gt;We ‘stream’ the response — and that means we can pass that streamed content back to the frontend so they’re not waiting for everything to finish before showing it to the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run the Flask Application
&lt;/h3&gt;

&lt;p&gt;If you’re inside IDX, you can just view it in the ‘preview’ window&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%2F8r6b128rhvaem27kjuts.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%2F8r6b128rhvaem27kjuts.png" alt="https://cdn-images-1.medium.com/max/800/1*Xl8qXDvkkDJ9PoozstwupA.png" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, you should be able to access your web application in your browser, select an image of a baked good, enter a prompt, and generate a baking recipe using the Gemini API. If you’re running inside of IDX, you can just run the ‘web preview’ — click Cmd+Shift+P and enter ‘Web preview’ (if on a mac) and you’ll see the preview window.&lt;/p&gt;

&lt;p&gt;Hit generate and you’ll see a recipe based on the image you’ve selected:&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%2Fzhv48lhefqrwt8n0u48y.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%2Fzhv48lhefqrwt8n0u48y.png" alt="https://cdn-images-1.medium.com/max/800/1*9hBTJhjNHWv4lmZ-yFvMAw.png" width="744" height="719"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Analysing the javascript
&lt;/h3&gt;

&lt;p&gt;If you look through main.js you’ll see this file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;streamGemini&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./gemini-api.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;promptInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[name="prompt"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.output&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onsubmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ev&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="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&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;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Generating...&lt;/span&gt;&lt;span class="dl"&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;// Load the image as a base64 string&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;imageUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;namedItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chosen-image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;imageBase64&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;imageUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;base64js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromByteArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

    &lt;span class="c1"&gt;// Assemble the prompt by combining the text with the chosen image&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;contents&lt;/span&gt; &lt;span class="o"&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;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;inline_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;mime_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;imageBase64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;promptInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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="c1"&gt;// Call the multimodal model, and get a stream of results&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;streamGemini&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini-1.5-flash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// or gemini-1.5-pro&lt;/span&gt;
      &lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Read from the stream and interpret the output as markdown&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;md&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;markdownit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&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="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&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;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;md&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&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="nf"&gt;join&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="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;e&lt;/span&gt;&lt;span class="p"&gt;)&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;innerHTML&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;hr&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;e&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;In it you can see that when the form is submit, it passes the model and the ‘contents’ — this contents object is what gemini expects and in python it mostly does a pass through.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req_body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GenerativeModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;req_body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s quite a straightforward setup — we base64 encode the image, upload it along with the prompt to gemini and then stream the response.&lt;/p&gt;

&lt;p&gt;You can also see the stream response is coming from the &lt;code&gt;gemini-api.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Calls the given Gemini model with the given image and/or text
 * parts, streaming output (as a generator function).
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;streamGemini&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini-1.5-flash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// or gemini-1.5-pro&lt;/span&gt;
  &lt;span class="nx"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;// Send the prompt to the Python backend&lt;/span&gt;
  &lt;span class="c1"&gt;// Call API defined in main.py&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/generate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contents&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;streamResponseChunks&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="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * A helper that streams text output chunks from a fetch() response.
 */&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;streamResponseChunks&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&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;CHUNK_SEPARATOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;processBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;streamDone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;flush&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;chunkSeparatorIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CHUNK_SEPARATOR&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;streamDone&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;chunkSeparatorIndex&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;flush&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;chunkSeparatorIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunkSeparatorIndex&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&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;chunkSeparatorIndex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunkSeparatorIndex&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;CHUNK_SEPARATOR&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="nx"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^data:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&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="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="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;text&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nx"&gt;text&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;flush&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&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;reader&lt;/span&gt; &lt;span class="o"&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;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getReader&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="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&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;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&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;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextDecoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextDecoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;processBuffer&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;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;releaseLock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;processBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file, the response is streamed from the server and rendered into markdown as the data comes back to the frontend.&lt;/p&gt;

&lt;h3&gt;
  
  
  That’s a wrap
&lt;/h3&gt;

&lt;p&gt;We’ve covered a fair bit in this hello-world application, explaining how to get gemini setup in a python and flask app inside IDX, how to stream the response and how to use images as part of it!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Build a serverless AI App with Cloud run, Python, Gemini and Vertex AI</title>
      <dc:creator>dllewellyn</dc:creator>
      <pubDate>Mon, 23 Sep 2024 17:57:54 +0000</pubDate>
      <link>https://dev.to/dllewellyn/build-a-serverless-ai-app-with-cloud-run-python-gemini-and-vertex-ai-2e2n</link>
      <guid>https://dev.to/dllewellyn/build-a-serverless-ai-app-with-cloud-run-python-gemini-and-vertex-ai-2e2n</guid>
      <description>&lt;p&gt;In this series of tutorials, we’re going to use a suite of google tools - AI, cloud and dev - in order to create and deploy an AI powered application. We’ll steer clear on chatbots, because they are a tedious in the extreme and focus on building something more interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a "Hello World" Flask Application with Google IDX and the Gemini API
&lt;/h2&gt;

&lt;p&gt;To get started, we're going to use the 'hello world' IDX - Gemini and flask application. This gives us a really quick way to get setup with some AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Setup
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Navigate to Google IDX:&lt;/strong&gt; Begin by accessing the Google IDX platform in your web browser.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Select the Gemini API Template:&lt;/strong&gt; From the IDX welcome screen, locate and click on the "Gemini API" template under the "Start something new with a template" section.&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%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fe5a468f7-59dc-4534-a7a4-389b9b31a37b%2F7e7a5f3e-0421-445b-b9aa-88b16d104ed7%2Fselect-gemini-api.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%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fe5a468f7-59dc-4534-a7a4-389b9b31a37b%2F7e7a5f3e-0421-445b-b9aa-88b16d104ed7%2Fselect-gemini-api.png" alt="select-gemini-api.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure New Workspace:&lt;/strong&gt; A "New Workspace" window will appear. Here, customise the following settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name your workspace:&lt;/strong&gt; Provide a descriptive name for your project, such as "hello-gemini."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment:&lt;/strong&gt; Choose the "Python Web App (Flask)" option from the dropdown menu.&lt;/li&gt;
&lt;/ul&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%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fe5a468f7-59dc-4534-a7a4-389b9b31a37b%2Fa881bf5a-6d00-4c2e-ba68-0c6cfa02e4c6%2Fconfigure-workspace.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%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fe5a468f7-59dc-4534-a7a4-389b9b31a37b%2Fa881bf5a-6d00-4c2e-ba68-0c6cfa02e4c6%2Fconfigure-workspace.png" alt="configure-workspace.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create Workspace:&lt;/strong&gt; Once configured, click the "Create" button to initialise the workspace creation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Await Setup Completion:&lt;/strong&gt; IDX will set up the necessary environment for your Flask application. This process might take a few moments, and a progress bar will indicate the status.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;With your workspace ready, you can proceed to develop your Flask application within the provided environment.&lt;/p&gt;

&lt;p&gt;This simple setup using Google IDX provides a foundation for building and deploying Flask applications that interact with the Gemini API.&lt;/p&gt;

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

&lt;p&gt;Go to &lt;a href="https://idx.google.com/" rel="noopener noreferrer"&gt;https://idx.google.com/&lt;/a&gt; - you'll need a google account to get started, but you'll also need it for the rest of the gemini/ vertex stuff too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking through the Hello World application
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Obtain a Gemini API Key
&lt;/h3&gt;

&lt;p&gt;Before you begin, you'll need an API key to access the Gemini API.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visit the &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;Google Cloud Console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Navigate to the 'API Keys' section.&lt;/li&gt;
&lt;li&gt;Click 'Create API Key'.&lt;/li&gt;
&lt;li&gt;Choose an existing Google Cloud project or create a new one.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy the generated API key. &lt;strong&gt;Remember to store your API key securely!&lt;/strong&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%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fe5a468f7-59dc-4534-a7a4-389b9b31a37b%2F36e14395-6740-4cb9-b0ed-9193565c3084%2Fcreating-gemini-api-key.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%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fe5a468f7-59dc-4534-a7a4-389b9b31a37b%2F36e14395-6740-4cb9-b0ed-9193565c3084%2Fcreating-gemini-api-key.png" alt="creating-gemini-api-key.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Set up the Flask Application
&lt;/h3&gt;

&lt;p&gt;Create a Python file (e.g., &lt;code&gt;main.py&lt;/code&gt;) and install the necessary libraries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.generativeai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;

&lt;span class="c1"&gt;# Replace 'YOUR_API_KEY' with your actual API key
&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ... (Rest of the code will be added in the following steps)
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create the HTML Template
&lt;/h3&gt;

&lt;p&gt;Create an HTML file (e.g., &lt;code&gt;index.html&lt;/code&gt;) to serve as the front-end for your web application. This template will display images of baked goods, an input field for the prompt, and a results section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Baking with the Gemini API&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Baking with the Gemini API&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"images/baked-good-1.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Baked Good 1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"images/baked-good-2.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Baked Good 2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"images/baked-good-3.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Baked Good 3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"prompt"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Provide an example recipe for the baked goods in:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"prompt"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"prompt"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"generateRecipe()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Go&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"results"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Results will appear here&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
        &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateRecipe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// ... (JavaScript code to handle user input and send requests to the Flask app)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Define Flask Routes and Functions
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;main.py&lt;/code&gt; file, define the routes and functions to handle requests from the front-end and interact with the Gemini API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build a serverless AI App with Cloud run, Python, Gemini and Vertex AI
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;This is part 1 in a series!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Show me the code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/dllewellyn/hello-gemini" rel="noopener noreferrer"&gt;https://github.com/dllewellyn/hello-gemini&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;In this series of tutorials, we’re going to use a suite of google tools — AI, cloud and dev — in order to create and deploy an AI powered application. We’ll steer clear on chatbots, because they are a tedious in the extreme and focus on building something more interesting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a “Hello World” Flask Application with Google IDX and the Gemini API
&lt;/h3&gt;

&lt;p&gt;To get started, we’re going to use the ‘hello world’ IDX — Gemini and flask application. This gives us a really quick way to get setup with some AI tooling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project Setup
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Navigate to Google IDX:&lt;/strong&gt; Begin by accessing the Google IDX platform in your web browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select the Gemini API Template:&lt;/strong&gt; From the IDX welcome screen, locate and click on the “Gemini API” template under the “Start something new with a template” section.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;!&lt;a href="https://cdn-images-1.medium.com/max/800/1*3UbWMkF7ijeiRDG11z7KDw.png" rel="noopener noreferrer"&gt;https://cdn-images-1.medium.com/max/800/1*3UbWMkF7ijeiRDG11z7KDw.png&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Configure New Workspace:&lt;/strong&gt; A “New Workspace” window will appear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name your workspace:&lt;/strong&gt; I’ve just called it “hello-gemini.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment:&lt;/strong&gt; Choose the “Python Web App (Flask)” option from the dropdown menu.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;!&lt;a href="https://cdn-images-1.medium.com/max/800/1*s9j9Z4H7RirlczOCKohNiw.png" rel="noopener noreferrer"&gt;https://cdn-images-1.medium.com/max/800/1*s9j9Z4H7RirlczOCKohNiw.png&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create Workspace:&lt;/strong&gt; Once configured, click the “Create” button to initialise the workspace creation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Await Setup Completion:&lt;/strong&gt; IDX will set up the necessary environment for your Flask application.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With your workspace ready, we’ve got a basic ‘gemini’ application&lt;/p&gt;

&lt;h3&gt;
  
  
  Looking through the Hello World application
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Obtain a Gemini API Key
&lt;/h3&gt;

&lt;p&gt;!&lt;a href="https://cdn-images-1.medium.com/max/800/1*c6tBFPL_FQN9mSHMu84AiQ.png" rel="noopener noreferrer"&gt;https://cdn-images-1.medium.com/max/800/1*c6tBFPL_FQN9mSHMu84AiQ.png&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before you begin, you’ll need an API key to access the Gemini API.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visit the &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;Google Cloud Console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Navigate to the ‘API Keys’ section.&lt;/li&gt;
&lt;li&gt;Click ‘Create API Key’.&lt;/li&gt;
&lt;li&gt;Choose an existing Google Cloud project or create a new one.&lt;/li&gt;
&lt;li&gt;Copy the generated API key. &lt;strong&gt;Remember to store your API key securely!&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Set up the Flask Application
&lt;/h3&gt;

&lt;p&gt;Check the existing app in &lt;code&gt;main.py&lt;/code&gt; — update this with your actual API key.&lt;/p&gt;

&lt;p&gt;This is the basic setup of the gemini API&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import os
import json
from google.generativeai import genai
from flask import Flask, render_template, request, jsonify

# Replace 'YOUR_API_KEY' with your actual API key
API_KEY = 'YOUR_API_KEY'
genai.configure(api_key=API_KEY)
app = Flask(__name__)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Check theHTML Template
&lt;/h3&gt;

&lt;p&gt;Take a look in &lt;code&gt;index.html&lt;/code&gt;to serve as the front-end for your web application. This template will display images of baked goods, an input field for the prompt, and a results section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Baking with the Gemini API&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Baking with the Gemini API&amp;lt;/h1&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;img src="images/baked-good-1.jpg" alt="Baked Good 1"&amp;gt;
        &amp;lt;img src="images/baked-good-2.jpg" alt="Baked Good 2"&amp;gt;
        &amp;lt;img src="images/baked-good-3.jpg" alt="Baked Good 3"&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;label for="prompt"&amp;gt;Provide an example recipe for the baked goods in:&amp;lt;/label&amp;gt;
        &amp;lt;input type="text" id="prompt" name="prompt"&amp;gt;
        &amp;lt;button onclick="generateRecipe()"&amp;gt;Go&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div id="results"&amp;gt;
        &amp;lt;h2&amp;gt;Results will appear here&amp;lt;/h2&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll see three images on the screen and a prompt input.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define Flask Routes and Functions
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;main.py&lt;/code&gt; file, define the routes and functions to handle requests from the front-end and interact with the Gemini API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@app.route('/')
def index():
  return render_template('index.html')

@app.route("/api/generate", methods=["POST"])
def generate_api():
    if request.method == "POST":
        try:
            req_body = request.get_json()
            content = req_body.get("contents")
            model = genai.GenerativeModel(model_name=req_body.get("model"))
            response = model.generate_content(content, stream=True)
            def stream():
                for chunk in response:
                    yield 'data: %s\n\n' % json.dumps({ "text": chunk.text })

            return stream(), {'Content-Type': 'text/event-stream'}

        except Exception as e:
            return jsonify({ "error": str(e) })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this block of code, you’ll notice that we take the ‘model’ from the input, and the ‘contents’ which have been fired to the API from javascript.&lt;/p&gt;

&lt;p&gt;We ‘stream’ the response — and that means we can pass that streamed content back to the frontend so they’re not waiting for everything to finish before showing it to the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run the Flask Application
&lt;/h3&gt;

&lt;p&gt;If you’re inside IDX, you can just view it in the ‘preview’ window&lt;/p&gt;

&lt;p&gt;!&lt;a href="https://cdn-images-1.medium.com/max/800/1*Xl8qXDvkkDJ9PoozstwupA.png" rel="noopener noreferrer"&gt;https://cdn-images-1.medium.com/max/800/1*Xl8qXDvkkDJ9PoozstwupA.png&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, you should be able to access your web application in your browser, select an image of a baked good, enter a prompt, and generate a baking recipe using the Gemini API. If you’re running inside of IDX, you can just run the ‘web preview’ — click Cmd+Shift+P and enter ‘Web preview’ (if on a mac) and you’ll see the preview window.&lt;/p&gt;

&lt;p&gt;Hit generate and you’ll see a recipe based on the image you’ve selected:&lt;/p&gt;

&lt;p&gt;!&lt;a href="https://cdn-images-1.medium.com/max/800/1*9hBTJhjNHWv4lmZ-yFvMAw.png" rel="noopener noreferrer"&gt;https://cdn-images-1.medium.com/max/800/1*9hBTJhjNHWv4lmZ-yFvMAw.png&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Analysing the javascript
&lt;/h3&gt;

&lt;p&gt;If you look through main.js you’ll see this file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { streamGemini } from './gemini-api.js';

let form = document.querySelector('form');
let promptInput = document.querySelector('input[name="prompt"]');
let output = document.querySelector('.output');

form.onsubmit = async (ev) =&amp;gt; {
  ev.preventDefault();
  output.textContent = 'Generating...';

  try {
    // Load the image as a base64 string
    let imageUrl = form.elements.namedItem('chosen-image').value;
    let imageBase64 = await fetch(imageUrl)
      .then(r =&amp;gt; r.arrayBuffer())
      .then(a =&amp;gt; base64js.fromByteArray(new Uint8Array(a)));

    // Assemble the prompt by combining the text with the chosen image
    let contents = [
      {
        role: 'user',
        parts: [
          { inline_data: { mime_type: 'image/jpeg', data: imageBase64, } },
          { text: promptInput.value }
        ]
      }
    ];

    // Call the multimodal model, and get a stream of results
    let stream = streamGemini({
      model: 'gemini-1.5-flash', // or gemini-1.5-pro
      contents,
    });

    // Read from the stream and interpret the output as markdown
    let buffer = [];
    let md = new markdownit();
    for await (let chunk of stream) {
      buffer.push(chunk);
      output.innerHTML = md.render(buffer.join(''));
    }
  } catch (e) {
    output.innerHTML += '&amp;lt;hr&amp;gt;' + e;
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In it you can see that when the form is submit, it passes the model and the ‘contents’ — this contents object is what gemini expects and in python it mostly does a pass through.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;content = req_body.get("contents")
model = genai.GenerativeModel(model_name=req_body.get("model"))
response = model.generate_content(content, stream=True)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s quite a straightforward setup — we base64 encode the image, upload it along with the prompt to gemini and then stream the response.&lt;/p&gt;

&lt;p&gt;You can also see the stream response is coming from the &lt;code&gt;gemini-api.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/**
 * Calls the given Gemini model with the given image and/or text
 * parts, streaming output (as a generator function).
 */
export async function* streamGemini({
  model = 'gemini-1.5-flash', // or gemini-1.5-pro
  contents = [],
} = {}) {
  // Send the prompt to the Python backend
  // Call API defined in main.py
  let response = await fetch("/api/generate", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ model, contents })
  });

  yield* streamResponseChunks(response);
}

/**
 * A helper that streams text output chunks from a fetch() response.
 */
async function* streamResponseChunks(response) {
  let buffer = '';

  const CHUNK_SEPARATOR = '\n\n';

  let processBuffer = async function* (streamDone = false) {
    while (true) {
      let flush = false;
      let chunkSeparatorIndex = buffer.indexOf(CHUNK_SEPARATOR);
      if (streamDone &amp;amp;&amp;amp; chunkSeparatorIndex &amp;lt; 0) {
        flush = true;
        chunkSeparatorIndex = buffer.length;
      }
      if (chunkSeparatorIndex &amp;lt; 0) {
        break;
      }

      let chunk = buffer.substring(0, chunkSeparatorIndex);
      buffer = buffer.substring(chunkSeparatorIndex + CHUNK_SEPARATOR.length);
      chunk = chunk.replace(/^data:\s*/, '').trim();
      if (!chunk) {
        if (flush) break;
        continue;
      }
      let { error, text } = JSON.parse(chunk);
      if (error) {
        console.error(error);
        throw new Error(error?.message || JSON.stringify(error));
      }
      yield text;
      if (flush) break;
    }
  };

  const reader = response.body.getReader();
  try {
    while (true) {
      const { done, value } = await reader.read()
      if (done) break;
      buffer += new TextDecoder().decode(value);
      console.log(new TextDecoder().decode(value));
      yield* processBuffer();
    }
  } finally {
    reader.releaseLock();
  }

  yield* processBuffer(true);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file, the response is streamed from the server and rendered into markdown as the data comes back to the frontend.&lt;/p&gt;

&lt;h3&gt;
  
  
  That’s a wrap
&lt;/h3&gt;

&lt;p&gt;We’ve covered a fair bit in this hello-world application, explaining how to get gemini setup in a python and flask app inside IDX, how to stream the response and how to use images as part of it!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;index.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/generate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_recipe&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;selected_image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;selected_image&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# ... (Code to process the image and prompt, and send a request to the Gemini API)
&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;models/gemini-1.5-pro-vision&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;image_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;stream_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# ... (Code to stream the response back to the front-end)
&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;streamed_response&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="sb"&gt;``&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;### Handle User Input and Display Results
&lt;/span&gt;
&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`index.html`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;JavaScript&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;send&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;display&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;streamed&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;Gemini&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="sb"&gt;``&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;javascript&lt;/span&gt;
&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Previous&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateRecipe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="n"&gt;selectedImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;selected&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/generate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`selected_image=${selectedImage}&amp;amp;prompt=${prompt}`&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="n"&gt;resultsDiv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;resultsDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;Clear&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;

            &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;display&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;streamed&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;resultsDiv&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="o"&gt;//&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt; &lt;span class="n"&gt;errors&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="nf"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt; &lt;span class="n"&gt;errors&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;
  
  
  Run the Flask Application
&lt;/h3&gt;

&lt;p&gt;Run your Flask app using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

python &lt;span class="nt"&gt;-m&lt;/span&gt; flask &lt;span class="nt"&gt;--app&lt;/span&gt; main run &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$PORT&lt;/span&gt; &lt;span class="nt"&gt;--debug&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Now, you should be able to access your web application in your browser, select an image of a baked good, enter a prompt, and generate a baking recipe using the Gemini API. If you're running inside of IDX, you can just run the 'web preview' - click Cmd+Shift+P and enter 'Web preview' (if on a mac) and you'll see the preview window.&lt;/p&gt;

&lt;h3&gt;
  
  
  A wrap for now
&lt;/h3&gt;

&lt;p&gt;That’s a wrap for now, in the next post we’ll talk through changing this to use vertex AI instead &lt;/p&gt;

&lt;h3&gt;
  
  
  Build a serverless AI App with Cloud run, Python, Gemini and Vertex AI
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;This is part 1 in a series!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Show me the code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/dllewellyn/hello-gemini" rel="noopener noreferrer"&gt;https://github.com/dllewellyn/hello-gemini&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;In this series of tutorials, we’re going to use a suite of google tools — AI, cloud and dev — in order to create and deploy an AI powered application. We’ll steer clear on chatbots, because they are a tedious in the extreme and focus on building something more interesting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a “Hello World” Flask Application with Google IDX and the Gemini API
&lt;/h3&gt;

&lt;p&gt;To get started, we’re going to use the ‘hello world’ IDX — Gemini and flask application. This gives us a really quick way to get setup with some AI tooling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project Setup
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Navigate to Google IDX:&lt;/strong&gt; Begin by accessing the Google IDX platform in your web browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select the Gemini API Template:&lt;/strong&gt; From the IDX welcome screen, locate and click on the “Gemini API” template under the “Start something new with a template” section.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;!&lt;a href="https://cdn-images-1.medium.com/max/800/1*3UbWMkF7ijeiRDG11z7KDw.png" rel="noopener noreferrer"&gt;https://cdn-images-1.medium.com/max/800/1*3UbWMkF7ijeiRDG11z7KDw.png&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Configure New Workspace:&lt;/strong&gt; A “New Workspace” window will appear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name your workspace:&lt;/strong&gt; I’ve just called it “hello-gemini.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment:&lt;/strong&gt; Choose the “Python Web App (Flask)” option from the dropdown menu.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;!&lt;a href="https://cdn-images-1.medium.com/max/800/1*s9j9Z4H7RirlczOCKohNiw.png" rel="noopener noreferrer"&gt;https://cdn-images-1.medium.com/max/800/1*s9j9Z4H7RirlczOCKohNiw.png&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create Workspace:&lt;/strong&gt; Once configured, click the “Create” button to initialise the workspace creation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Await Setup Completion:&lt;/strong&gt; IDX will set up the necessary environment for your Flask application.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With your workspace ready, we’ve got a basic ‘gemini’ application&lt;/p&gt;

&lt;h3&gt;
  
  
  Looking through the Hello World application
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Obtain a Gemini API Key
&lt;/h3&gt;

&lt;p&gt;!&lt;a href="https://cdn-images-1.medium.com/max/800/1*c6tBFPL_FQN9mSHMu84AiQ.png" rel="noopener noreferrer"&gt;https://cdn-images-1.medium.com/max/800/1*c6tBFPL_FQN9mSHMu84AiQ.png&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before you begin, you’ll need an API key to access the Gemini API.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visit the &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;Google Cloud Console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Navigate to the ‘API Keys’ section.&lt;/li&gt;
&lt;li&gt;Click ‘Create API Key’.&lt;/li&gt;
&lt;li&gt;Choose an existing Google Cloud project or create a new one.&lt;/li&gt;
&lt;li&gt;Copy the generated API key. &lt;strong&gt;Remember to store your API key securely!&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Set up the Flask Application
&lt;/h3&gt;

&lt;p&gt;Check the existing app in &lt;code&gt;main.py&lt;/code&gt; — update this with your actual API key.&lt;/p&gt;

&lt;p&gt;This is the basic setup of the gemini API&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import os
import json
from google.generativeai import genai
from flask import Flask, render_template, request, jsonify

# Replace 'YOUR_API_KEY' with your actual API key
API_KEY = 'YOUR_API_KEY'
genai.configure(api_key=API_KEY)
app = Flask(__name__)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Check theHTML Template
&lt;/h3&gt;

&lt;p&gt;Take a look in &lt;code&gt;index.html&lt;/code&gt;to serve as the front-end for your web application. This template will display images of baked goods, an input field for the prompt, and a results section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Baking with the Gemini API&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Baking with the Gemini API&amp;lt;/h1&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;img src="images/baked-good-1.jpg" alt="Baked Good 1"&amp;gt;
        &amp;lt;img src="images/baked-good-2.jpg" alt="Baked Good 2"&amp;gt;
        &amp;lt;img src="images/baked-good-3.jpg" alt="Baked Good 3"&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;label for="prompt"&amp;gt;Provide an example recipe for the baked goods in:&amp;lt;/label&amp;gt;
        &amp;lt;input type="text" id="prompt" name="prompt"&amp;gt;
        &amp;lt;button onclick="generateRecipe()"&amp;gt;Go&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div id="results"&amp;gt;
        &amp;lt;h2&amp;gt;Results will appear here&amp;lt;/h2&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll see three images on the screen and a prompt input.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define Flask Routes and Functions
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;main.py&lt;/code&gt; file, define the routes and functions to handle requests from the front-end and interact with the Gemini API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@app.route('/')
def index():
  return render_template('index.html')

@app.route("/api/generate", methods=["POST"])
def generate_api():
    if request.method == "POST":
        try:
            req_body = request.get_json()
            content = req_body.get("contents")
            model = genai.GenerativeModel(model_name=req_body.get("model"))
            response = model.generate_content(content, stream=True)
            def stream():
                for chunk in response:
                    yield 'data: %s\n\n' % json.dumps({ "text": chunk.text })

            return stream(), {'Content-Type': 'text/event-stream'}

        except Exception as e:
            return jsonify({ "error": str(e) })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this block of code, you’ll notice that we take the ‘model’ from the input, and the ‘contents’ which have been fired to the API from javascript.&lt;/p&gt;

&lt;p&gt;We ‘stream’ the response — and that means we can pass that streamed content back to the frontend so they’re not waiting for everything to finish before showing it to the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run the Flask Application
&lt;/h3&gt;

&lt;p&gt;If you’re inside IDX, you can just view it in the ‘preview’ window&lt;/p&gt;

&lt;p&gt;!&lt;a href="https://cdn-images-1.medium.com/max/800/1*Xl8qXDvkkDJ9PoozstwupA.png" rel="noopener noreferrer"&gt;https://cdn-images-1.medium.com/max/800/1*Xl8qXDvkkDJ9PoozstwupA.png&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, you should be able to access your web application in your browser, select an image of a baked good, enter a prompt, and generate a baking recipe using the Gemini API. If you’re running inside of IDX, you can just run the ‘web preview’ — click Cmd+Shift+P and enter ‘Web preview’ (if on a mac) and you’ll see the preview window.&lt;/p&gt;

&lt;p&gt;Hit generate and you’ll see a recipe based on the image you’ve selected:&lt;/p&gt;

&lt;p&gt;!&lt;a href="https://cdn-images-1.medium.com/max/800/1*9hBTJhjNHWv4lmZ-yFvMAw.png" rel="noopener noreferrer"&gt;https://cdn-images-1.medium.com/max/800/1*9hBTJhjNHWv4lmZ-yFvMAw.png&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Analysing the javascript
&lt;/h3&gt;

&lt;p&gt;If you look through main.js you’ll see this file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { streamGemini } from './gemini-api.js';

let form = document.querySelector('form');
let promptInput = document.querySelector('input[name="prompt"]');
let output = document.querySelector('.output');

form.onsubmit = async (ev) =&amp;gt; {
  ev.preventDefault();
  output.textContent = 'Generating...';

  try {
    // Load the image as a base64 string
    let imageUrl = form.elements.namedItem('chosen-image').value;
    let imageBase64 = await fetch(imageUrl)
      .then(r =&amp;gt; r.arrayBuffer())
      .then(a =&amp;gt; base64js.fromByteArray(new Uint8Array(a)));

    // Assemble the prompt by combining the text with the chosen image
    let contents = [
      {
        role: 'user',
        parts: [
          { inline_data: { mime_type: 'image/jpeg', data: imageBase64, } },
          { text: promptInput.value }
        ]
      }
    ];

    // Call the multimodal model, and get a stream of results
    let stream = streamGemini({
      model: 'gemini-1.5-flash', // or gemini-1.5-pro
      contents,
    });

    // Read from the stream and interpret the output as markdown
    let buffer = [];
    let md = new markdownit();
    for await (let chunk of stream) {
      buffer.push(chunk);
      output.innerHTML = md.render(buffer.join(''));
    }
  } catch (e) {
    output.innerHTML += '&amp;lt;hr&amp;gt;' + e;
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In it you can see that when the form is submit, it passes the model and the ‘contents’ — this contents object is what gemini expects and in python it mostly does a pass through.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;content = req_body.get("contents")
model = genai.GenerativeModel(model_name=req_body.get("model"))
response = model.generate_content(content, stream=True)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s quite a straightforward setup — we base64 encode the image, upload it along with the prompt to gemini and then stream the response.&lt;/p&gt;

&lt;p&gt;You can also see the stream response is coming from the &lt;code&gt;gemini-api.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/**
 * Calls the given Gemini model with the given image and/or text
 * parts, streaming output (as a generator function).
 */
export async function* streamGemini({
  model = 'gemini-1.5-flash', // or gemini-1.5-pro
  contents = [],
} = {}) {
  // Send the prompt to the Python backend
  // Call API defined in main.py
  let response = await fetch("/api/generate", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ model, contents })
  });

  yield* streamResponseChunks(response);
}

/**
 * A helper that streams text output chunks from a fetch() response.
 */
async function* streamResponseChunks(response) {
  let buffer = '';

  const CHUNK_SEPARATOR = '\n\n';

  let processBuffer = async function* (streamDone = false) {
    while (true) {
      let flush = false;
      let chunkSeparatorIndex = buffer.indexOf(CHUNK_SEPARATOR);
      if (streamDone &amp;amp;&amp;amp; chunkSeparatorIndex &amp;lt; 0) {
        flush = true;
        chunkSeparatorIndex = buffer.length;
      }
      if (chunkSeparatorIndex &amp;lt; 0) {
        break;
      }

      let chunk = buffer.substring(0, chunkSeparatorIndex);
      buffer = buffer.substring(chunkSeparatorIndex + CHUNK_SEPARATOR.length);
      chunk = chunk.replace(/^data:\s*/, '').trim();
      if (!chunk) {
        if (flush) break;
        continue;
      }
      let { error, text } = JSON.parse(chunk);
      if (error) {
        console.error(error);
        throw new Error(error?.message || JSON.stringify(error));
      }
      yield text;
      if (flush) break;
    }
  };

  const reader = response.body.getReader();
  try {
    while (true) {
      const { done, value } = await reader.read()
      if (done) break;
      buffer += new TextDecoder().decode(value);
      console.log(new TextDecoder().decode(value));
      yield* processBuffer();
    }
  } finally {
    reader.releaseLock();
  }

  yield* processBuffer(true);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file, the response is streamed from the server and rendered into markdown as the data comes back to the frontend.&lt;/p&gt;

&lt;h3&gt;
  
  
  That’s a wrap
&lt;/h3&gt;

&lt;p&gt;We’ve covered a fair bit in this hello-world application, explaining how to get gemini setup in a python and flask app inside IDX, how to stream the response and how to use images as part of it!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Gen AI + Already proven practices = better Dev teams</title>
      <dc:creator>dllewellyn</dc:creator>
      <pubDate>Mon, 23 Sep 2024 09:09:03 +0000</pubDate>
      <link>https://dev.to/dllewellyn/gen-ai-already-proven-practices-better-dev-teams-11ee</link>
      <guid>https://dev.to/dllewellyn/gen-ai-already-proven-practices-better-dev-teams-11ee</guid>
      <description>&lt;p&gt;Using Generative AI to change how we do product engineer&lt;/p&gt;

&lt;h1&gt;
  
  
  A failure of imagination
&lt;/h1&gt;

&lt;p&gt;Since the Generative AI starting gun was first fired, we've started to see a lot of people trying to do the things they already do better, but using AI. To be fair, the results are positive - the pragmatic engineer laid out some of the ways that people are already trying to use generative AI, especially github co-pilot, to make them more productive&lt;/p&gt;

&lt;p&gt;&lt;a href="https://newsletter.pragmaticengineer.com/p/ai-tooling-2024" rel="noopener noreferrer"&gt;https://newsletter.pragmaticengineer.com/p/ai-tooling-2024&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But what's missing, is re-thinking the whole process from scratch&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If I ask people what they wanted, they'd have said a faster horse - Henry Ford &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's hard to break out from the constraints that come from working in a particular environment, in a particular way for a long time - developers will look at how they can make themselves more productive and write higher quality software, product managers will look at how they can  research better or build better personas, business analysts will look at capturing requirements and writing better tickets, and designers will look at how they can build their high-fidelity stories better. &lt;/p&gt;

&lt;p&gt;Without getting people out of thinking of their current jobs, the plateau from AI is likely to come really quickly - its probably not going to be trusted to build large complex software any time soon, so all it can do it is make it a bit faster (or maybe a lot faster). &lt;/p&gt;

&lt;p&gt;There are probably a few killer ideas out there, and I'm sure we're going to see some revolutionary new approach soon - but in the mean time, there are ideas that we &lt;em&gt;could and should already be doing&lt;/em&gt;, which have already been proven to work, and which are much easier to adopt when you have AI to help.&lt;/p&gt;

&lt;p&gt;There are also some ideas that are currently acknowledged as being things we shouldn't do, writing comments for every function is a good example, but the desire to do so persists - there is value in it, and AI can likely help to make it easier, more effective and more palatable. &lt;/p&gt;

&lt;h1&gt;
  
  
  Things we should already be doing
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Small teams
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The problem
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Brooks's law is an observation about software project management that "Adding manpower to a late software project makes it later." - Mythical Man Month&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We see the concepts of small teams over and over, it's a core part of Agile and DevOps, 'Getting real' even advocate a team as small as 3 for version 1.0. That small teams are needed isn't really that controversial - the challenge comes when we add two other factors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Teams should be mostly self-sufficient - Accelerate demonstrates that hand-offs to separate QA teams for testing are bad, are architecture review boards are bad. The entire concept of DevOps rests on the idea that dev and ops hand-offs are bad.&lt;/li&gt;
&lt;li&gt;We have a lot of job-titles and specialisms - If we need a few cloud engineers for the infrastructure, an architect to design it, a UI/UX designer for the UI design, a few frontend engineers for the frontend, a few backend engineers for the backend, a security person, a business analyst, a product manager, someone to look after delivery, a QA for testing, maybe an automation engineer for the pipelines (and on and on it goes) - how can we ever include all those roles in one team?
### The solution &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The logical answer is to make your team a team of generalists.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Small teams need people who can wear different hats. You need designers who can write. You need programmers who understand design. - Getting Real, Signal37 &lt;/p&gt;

&lt;p&gt;By cross-training and growing engineering skills, generalists can do orders of magnitude more work than their specialist counterparts and it also improves our overall flow of work by removing queues and wait times - The DevOps Handbook - Gene Kim et al&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, the solution is easy - make everyone generalists right? This is as much a cultural challenge as anything else; but from a purely skills-based point of view there's a great opportunity to use AI. &lt;/p&gt;

&lt;h3&gt;
  
  
  AI as a solution
&lt;/h3&gt;

&lt;p&gt;People might balk at the idea of asking AI to help find security issues, asses design against user personas, look for edge cases when using API libraries, generate automated tests or help write IaC - but by focusing on 'knowing when to ask for help' rather than knowing how to do everything perfectly, it means that you end up with much more efficient teams that are much more likely to focus on the right tasks at the right time. &lt;/p&gt;

&lt;p&gt;This is obviously controversial, as is the basic idea of a full-stack engineer, but I would argue with some discipline from the individual engineers to ensure they understand what they've generated, and to get AI to double check things, it's a perfectly safe and very productive way to work.&lt;/p&gt;

&lt;p&gt;One thing that AI can't help with though (or maybe I'm just drawing a blank...), is the cultural changes needed to make it happen&lt;/p&gt;

&lt;h2&gt;
  
  
  Design to Dev hand-offs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The problem
&lt;/h3&gt;

&lt;p&gt;Let's expand on the generalists point, to include not just engineers but other roles in the team. DevOps is heavily influenced by the idea of how difficult handing off from dev to ops is, but design to dev is hardly a smooth transition - especially if weeks went into finalising the design and agreeing it with stakeholders. &lt;/p&gt;

&lt;h3&gt;
  
  
  The idea
&lt;/h3&gt;

&lt;p&gt;There's already another school of thought on this:&lt;/p&gt;

&lt;p&gt;In their book "Getting real" (&lt;a href="https://basecamp.com/gettingreal/06.3-from-idea-to-implementation" rel="noopener noreferrer"&gt;https://basecamp.com/gettingreal/06.3-from-idea-to-implementation&lt;/a&gt;) the 37 Signals team lay out their process for getting to new features:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Paper sketches &amp;gt; Create HTML screens &amp;gt; Code It &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Throughout the book, they emphasise the going straight from paper sketches to HTML - a sentiment that is repeated in rework and is evident in their hotwired suite of open source tools. &lt;/p&gt;

&lt;p&gt;Getting some HTML, CSS and javascript out to customers for them to try out is a much more true test than 'clickable' prototypes. 37Signals advocate the idea of having devs doing a bit of design, and designers doing bits of html - that can be a bit of a tall order, but... &lt;/p&gt;

&lt;h3&gt;
  
  
  AI as a solution
&lt;/h3&gt;

&lt;p&gt;With some prompting, designer's don't even need to learn much html/css. Some of my colleagues at Create Future (&lt;a class="mentioned-user" href="https://dev.to/angus"&gt;@angus&lt;/a&gt; Allan in particular) have produced some pretty impressive websites and games with just prompting - so, what if we skip the 'high fidelity' designs that get passed over to developers? AI makes it's easy to change too, you can sit with a customer live and modify your page, refresh - "How's that?" - much better to iterate in minutes than in weeks. &lt;/p&gt;

&lt;p&gt;Even better, you can take your hand-drawn sketches, upload to chat GPT and ask for it to turn it into a website for you. &lt;/p&gt;

&lt;h2&gt;
  
  
  Picking the right tech-stack for the problem
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The problem
&lt;/h3&gt;

&lt;p&gt;In monolithic architectures and code-bases, we usually have to pick a single tech-stack and accept that it's going to be the best fit for some things, and not for others. If we're lucky, we optimised for the big problems - getting the best tech stack for the problems we face most often; if we're not - we're battling our setup to make it work better &lt;/p&gt;

&lt;h3&gt;
  
  
  The solution
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;With a system composed of multiple, collaborating microservices, we can decide to use different technologies inside each one. This allows us to pick the right tool for each job rather than having to &lt;strong&gt;select a more standardized, one-size-fits-all approach that often ends up being the lowest common denominator.&lt;/strong&gt; - Sam Newman, Building Microservices, 2nd Edition &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But what happens in reality is that teams tend to pick a particular stack and only work in that. Sometimes the driver is outside the team - it's reasonable to want to make it easy for engineers to move between teams and not have the programming language or tech as a barrier, but often it's from the teams themselves - people just pick the tech they know. &lt;/p&gt;

&lt;h3&gt;
  
  
  AI as a solution
&lt;/h3&gt;

&lt;p&gt;One of the benefits of micro-services is allowing teams to pick the best tech for their specific problem. Whilst it's  difficult to break people out of the comfort of their tech stack - gen AI can at least help with the skills. &lt;/p&gt;

&lt;p&gt;If you 'know enough' of a coding language to get things done, AI can help find various issues in you're code, if you don't know much about the programming language's ecosystem you can research various libraries people use, assess your code against best practices, suggest how you might convert from a language you know to one you don't, debug code or explain how you can debug it. &lt;/p&gt;

&lt;p&gt;It can also help with picking up other people's projects - all the big providers of AI coding assistants have a /explain option. Probably the biggest thing it can do though, is help teams make the right decision in the first place - outlining your service and its functionality, and getting suggestions from AI about what the best technology to use can take away some of the habitual nature of tech selection that engineers often exhibit. &lt;/p&gt;

&lt;h1&gt;
  
  
  Challenging existing assumptions
&lt;/h1&gt;

&lt;p&gt;As well as making some of the 'best practices' (or just alternative ways) more do-able, gen AI can start to challenge some of our the existing assumptions and make us more productive by adopting things we currently think of as a bit antiquated now &lt;/p&gt;

&lt;h2&gt;
  
  
  Don't write comments
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Why am I so down on comments? Because they lie. Not always, and not intentionally but too often. The older a comment is, the further it is from the code it described - Clean Code, Uncle Bob &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's difficult to argue with the statement that comments don't get updated and reviewed in the same way as regular code - but one neat feature in intellij AI assistant is to generate doc comments from the function itself. What if every time you saved a file, it checks if your comment is still true and just updates it if not? &lt;/p&gt;

&lt;p&gt;This is sort-of useful for most development, it means that you can get some good information about what a function does without reading it - it's really useful for public APIs where you often do that, and it's super useful for documenting UI components and other types of 'interface' with your team. &lt;/p&gt;

&lt;h1&gt;
  
  
  Functional specs are fantasies
&lt;/h1&gt;

&lt;p&gt;Architectural diagrams, UIs in figma, documentation, threat models, test cases, runbooks and application catalogues  - they're all undoubtedly very useful, but the problem is that even the most simple project has multiple versions of the truth. The common approach to this for a long while has been to assume nothing is true, spend onerous amounts of time reconciling it with reality, or trying (valiantly, but ultimately, unsuccessfully) to generate it automatically from code.&lt;/p&gt;

&lt;p&gt;We should re-visit these things, we could scan jira tickets, test tickets, videos of demos, designs and look for inaccuracies and update them automatically with gen AI - the real benefit is that the people who need the docs (and they really do sometimes) have them, and people can trust what they're looking at is up-to-date&lt;/p&gt;

&lt;h1&gt;
  
  
  Putting it all together
&lt;/h1&gt;

&lt;p&gt;So what do you have if you put it all together?&lt;/p&gt;

&lt;p&gt;You have a smaller team, capable of doing more tasks within them - your designers upload their hand-drawn sketches to chatGPT and turn it into HTML to test with end-users, they then use documentation automatically generated from components and open API specs to make it functional. &lt;/p&gt;

&lt;p&gt;When they're done, the devs can quickly clean it up while the team converts the ideas into tickets without too much hassle. &lt;/p&gt;

&lt;p&gt;The devs are programming in languages that are really optimised for the jobs that they are doing, and are living the devops dream - getting AI to help with tests, IaC and all the other benefits of gen AI. They release the code, and it updates architecture diagrams, flags anything that wasn't in the original designs / tickets, highlights any test cases that are missing - and then all the other supporting teams have their docs and user guides ready to roll. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;It's probably not the end-state, but it's a good start&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>What bits of software engineering faff would you want to get rid of?</title>
      <dc:creator>dllewellyn</dc:creator>
      <pubDate>Sun, 22 Sep 2024 12:33:00 +0000</pubDate>
      <link>https://dev.to/dllewellyn/what-bits-of-software-engineering-faff-would-you-want-to-get-rid-of-1pfk</link>
      <guid>https://dev.to/dllewellyn/what-bits-of-software-engineering-faff-would-you-want-to-get-rid-of-1pfk</guid>
      <description>&lt;p&gt;I was having to do a bunch of how to videos recently and then to convert them to written tutorials with images I made a little Web app to generate user guides from videos.&lt;/p&gt;

&lt;p&gt;It got me thinking though, what other things do devs have to faff with that they'd rather just film and describe ? I had a few ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate test cases for features&lt;/li&gt;
&lt;li&gt;Release notes&lt;/li&gt;
&lt;li&gt;SOPs / Runbooks&lt;/li&gt;
&lt;li&gt;Architecture diagram update&lt;/li&gt;
&lt;li&gt;Onboarding docs&lt;/li&gt;
&lt;li&gt;User manuals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What does everyone else think?&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%2Fdqh3bx7dep20szm7h72r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqh3bx7dep20szm7h72r.jpg" alt=" " width="800" height="1777"&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%2Fnjmeibj6dze0ojfue6iu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjmeibj6dze0ojfue6iu.jpg" alt=" " width="800" height="1777"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
